<!-- 博客 - 页面布局 > 上中下 -->



<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=dark>
<!-- 博客 - 导航栏上方 -->


<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/blog/img/fluid.png">
  <link rel="icon" href="/blog/img/icon/dw.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="Memory">
  <meta name="keywords" content="">

  
    <meta name="description" content="Memory 缘忆交友社区的开发流程">
<meta property="og:type" content="article">
<meta property="og:title" content="项目志录：Memory 缘忆交友社区开发文档">
<meta property="og:url" content="https://test.atomgit.net/blog/2023/09/10/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20%E7%BC%98%E5%BF%86%E4%BA%A4%E5%8F%8B%E7%A4%BE%E5%8C%BA%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/index.html">
<meta property="og:site_name" content="Memory&#39;s blog">
<meta property="og:description" content="Memory 缘忆交友社区的开发流程">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://test.atomgit.net/blog/img/artical/cloud.jpg">
<meta property="article:published_time" content="2023-09-10T06:39:03.000Z">
<meta property="article:modified_time" content="2023-09-26T15:17:22.000Z">
<meta property="article:author" content="Memory">
<meta property="article:tag" content="Redis">
<meta property="article:tag" content="项目">
<meta property="article:tag" content="开发经验">
<meta property="article:tag" content="Elasticsearch">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://test.atomgit.net/blog/img/artical/cloud.jpg">
  
  
  
  <title>项目志录：Memory 缘忆交友社区开发文档 - Memory&#39;s blog</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/blog/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/blog/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/blog/css/highlight-dark.css" />
  



  
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css">



  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"test.atomgit.net","root":"/blog/","version":"1.9.4","typing":{"enable":true,"typeSpeed":100,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":false,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"appId":"T1hUHH9Ks1ggG9DBE3HgUPpV-gzGzoHsz","appKey":"XFkgzzvzNo3J3uNCbBiSOFKc","server_url":"https://t1huhh9k.lc-cn-n1-shared.com","path":"window.location.pathname","ignore_local":false}},"search_path":"/blog/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/blog/js/utils.js" ></script>
  <script  src="/blog/js/color-schema.js" ></script>
  

  

  

  

  

  

  

  



  
<meta name="generator" content="Hexo 5.4.2"></head>

<body>
  

  <header>
    <!-- 博客 - 导航栏上方 -->


<div class="header-inner" style="height: 100vh">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/blog/">
      <strong>Memory&#39;s blog</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/">
                <i class="iconfont icon-home-fill"></i>
                <span>首页</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/archives/">
                <i class="iconfont icon-archive-fill"></i>
                <span>归档</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/categories/">
                <i class="iconfont icon-category-fill"></i>
                <span>分类</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/tags/">
                <i class="iconfont icon-tags-fill"></i>
                <span>标签</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/about/">
                <i class="iconfont icon-user-fill"></i>
                <span>关于</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/links/">
                <i class="iconfont icon-link-fill"></i>
                <span>友链</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/playlist/">
                <i class="iconfont icon-music"></i>
                <span>音乐</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/Memos/">
                <i class="iconfont icon-comment "></i>
                <span>说说</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/App/">
                <i class="iconfont icon-mobile"></i>
                <span>咫尺</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item dropdown">
              <a class="nav-link dropdown-toggle" target="_self" href="javascript:;" role="button"
                 data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <i class="iconfont icon-books"></i>
                <span>青简</span>
              </a>
              <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                
                  
                  
                  
                  <a class="dropdown-item" target="_blank" rel="noopener" href="http://47.93.135.62:5230/explore">
                    <i class="iconfont icon-images"></i>
                    <span>碎念微光</span>
                  </a>
                
                  
                  
                  
                  <a class="dropdown-item" href="/blog/New/">
                    <i class="iconfont icon-bug"></i>
                    <span>匠册详析</span>
                  </a>
                
                  
                  
                  
                  <a class="dropdown-item" href="/blog/New/">
                    <i class="iconfont icon-xbox-fill"></i>
                    <span>窗景新编</span>
                  </a>
                
                  
                  
                  
                  <a class="dropdown-item" href="/blog/New/">
                    <i class="iconfont icon-steam"></i>
                    <span>戏码幕后</span>
                  </a>
                
                  
                  
                  
                  <a class="dropdown-item" href="/blog/New/">
                    <i class="iconfont icon-map"></i>
                    <span>他山随拾</span>
                  </a>
                
              </div>
            </li>
          
        
        
            <li class="nav-item" id="search-btn">
                <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalNotice" aria-label="Search">
                    <i class="iconfont icon-book"></i>
                    <span>公告</span>
                </a>
            </li>
            
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              <i class="iconfont icon-search"></i>
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">
              <i class="iconfont icon-dark" id="color-toggle-icon"></i>
            </a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>
 
<div id="banner" class="banner" parallax=true
     style="background: url('/blog/img/artical/cloud.jpg') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">

    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">

        <div class="h2">
          
            <span id="subtitle" data-typed-text="项目志录：Memory 缘忆交友社区开发文档"></span>
          
        </div>


        
          <!-- 文章页面 - 顶部信息 -->

<div class="mt-3">
  
  <span class="post-meta mr-2">
    <i class="iconfont icon-author" aria-hidden="true"></i>
    Memory
  </span>
   
  <span class="post-meta">
    <i class="iconfont icon-date-fill" aria-hidden="true"></i>
    <time datetime="2023-09-10 14:39" pubdate>
      2023年9月10日 下午
    </time>
  </span>
  
</div>

<div class="mt-1">
  
  <span class="post-meta mr-2">
    <i class="iconfont icon-chart"></i>
     32k 字 
  </span>
   
  <span class="post-meta mr-2">
    <i class="iconfont icon-clock-fill"></i>
       80 分钟 
  </span>
     
  <span
    id="leancloud-page-views-container"
    class="post-meta"
    style="display: none"
  >
    <i class="iconfont icon-eye" aria-hidden="true"></i>
    <span id="leancloud-page-views"></span> 次
  </span>

    
</div>


        

        <!-- 添加网站运行时间 -->
        <div class="footer-content" style="background-color: transparent;">
          <div>
            <span id="timeDate"></span>
            <span id="times"></span>
            <script>
            var now = new Date();
            function createtime(){
                var grt= new Date("03/07/2023 00:00:00");//此处修改你的建站时间或者网站上线时间
                now.setTime(now.getTime()+250);
                days = (now - grt ) / 1000 / 60 / 60 / 24;
                dnum = Math.floor(days);
                hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
                hnum = Math.floor(hours);
                if(String(hnum).length ==1 ){
                    hnum = "0" + hnum;
                }
                minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
                mnum = Math.floor(minutes);
                if(String(mnum).length ==1 ){
                          mnum = "0" + mnum;
                }
                seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
                snum = Math.round(seconds);
                if(String(snum).length ==1 ){
                          snum = "0" + snum;
                }
                document.getElementById("timeDate").innerHTML = "🚀 &nbsp"+dnum+"&nbsp天";  //此次自定义显示内容
                document.getElementById("times").innerHTML = hnum + "&nbsp小时&nbsp" + mnum + "&nbsp分&nbsp" + snum + "&nbsp秒";
            }  

            if (!window.location.href.includes('/20')) {  
            setInterval("createtime()", 250);  // 只有当URL不包含'specific-page'时，才调用createtime  
            }//此次自定义显示内容
            </script>
          </div>
        </div>

      </div>
      
        <div class="scroll-down-bar">
          <i class="iconfont icon-arrowdown"></i>
        </div>
      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      <!-- 文章页  -->


<div class="container-fluid nopadding-x">

  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar category-bar" style="margin-right: -1rem">
    <!-- 文章页面 - 左边栏所属分类 -->
  

<!-- 文章页 - 文章分类 -->





<div class="category-list">
  
  
    
    
    
    <div class="category row nomargin-x">
      <a class="category-item 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="项目志录"
        id="heading-005e56101bc2a873707cb4461a70ded8" role="tab" data-toggle="collapse" href="#collapse-005e56101bc2a873707cb4461a70ded8"
        aria-expanded="true"
      >
        项目志录
        <span class="list-group-count">(12)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-005e56101bc2a873707cb4461a70ded8"
           role="tabpanel" aria-labelledby="heading-005e56101bc2a873707cb4461a70ded8">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/blog/2023/03/17/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20%E7%94%A8%E6%88%B7%E4%B8%AD%E5%BF%83%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory 用户中心中心开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">项目志录：Memory 用户中心中心开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/03/24/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20%E4%BC%99%E4%BC%B4%E5%8C%B9%E9%85%8D%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory 伙伴匹配系统开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">项目志录：Memory 伙伴匹配系统开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/04/09/%E9%BB%84%E9%87%91%E7%9F%BF%E5%B7%A5%E6%80%80%E6%97%A7%E7%89%88-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="黄金矿工怀旧版-开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">黄金矿工怀旧版-开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/04/22/%E9%A1%B9%E7%9B%AE%E5%9B%9E%E9%A1%BE%20-%20%E5%AE%9E%E8%B7%B5%E7%BB%8F%E9%AA%8C%E5%92%8C%E6%94%B6%E8%8E%B7%E6%80%BB%E7%BB%93/" title="项目回顾 - 实践经验和收获总结"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">项目回顾 - 实践经验和收获总结</span>
        </a>
      
    
      
      
        <a href="/blog/2023/07/11/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20API%20%E6%8E%A5%E5%8F%A3%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory API 接口开放平台开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">项目志录：Memory API 接口开放平台开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/08/02/PicMemories%20%E5%A3%81%E7%BA%B8%E5%88%86%E4%BA%AB%E5%B0%8F%E7%A8%8B%E5%BA%8F%20-%20%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="PicMemories-开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">PicMemories-开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/08/07/TaskMemories%20-%20%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="TaskMemories-开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">TaskMemories-开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/08/26/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20Search%20%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory Search 聚合搜索平台开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">项目志录：Memory Search 聚合搜索平台开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/09/10/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20%E7%BC%98%E5%BF%86%E4%BA%A4%E5%8F%8B%E7%A4%BE%E5%8C%BA%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory 缘忆交友社区开发文档"
           class="list-group-item list-group-item-action
           active">
          <span class="category-post">项目志录：Memory 缘忆交友社区开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/2023/10/03/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20BI%20%E6%99%BA%E8%83%BD%E5%88%86%E6%9E%90%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory BI 智能分析平台开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">项目志录：Memory BI 智能分析平台开发文档</span>
        </a>
      
    
      
      
        <a href="/blog/categories/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95/" class="list-group-item list-group-item-action">
          <span class="category-post">More...</span>
        </a>
        
  </div>

        
      </div>
    </div>
  
</div>

  </aside>


    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">项目志录：Memory 缘忆交友社区开发文档</h1>
            
              <p class="note note-success">
                
                  
                    本文最后更新于：2 年前
                  
                
              </p>
            
            
              <div class="markdown-body">
                
                <h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><ul>
<li>项目概述：对项目的整体简介，包括项目的目的、背景和主要功能。</li>
<li>目标受众：定义项目的受众群体，如用户、开发人员、测试人员等。</li>
</ul>
<h1 id="技术栈"><a href="#技术栈" class="headerlink" title="技术栈"></a>技术栈</h1><h2 id="前端技术栈"><a href="#前端技术栈" class="headerlink" title="前端技术栈"></a>前端技术栈</h2><ul>
<li><p>Ant Design Vue</p>
</li>
<li><p>@vue/cli 脚手架</p>
</li>
<li><p>Vant UI 组件库</p>
</li>
<li><p>TypeScript</p>
</li>
<li><p>Axios 请求库</p>
</li>
</ul>
<h2 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h2><ul>
<li>Java SpringBoot 2.7.x 框架</li>
<li>MySQL 数据库</li>
<li>MyBatis-Plus</li>
<li>MyBatis X 自动生成</li>
<li>Redis 缓存（Spring Data Redis 等多种实现方式）</li>
<li>Redisson 分布式锁</li>
<li>Easy Excel 数据导入</li>
<li>Spring Scheduler 定时任务</li>
<li>Swagger + Knife4j 接口文档</li>
<li>Gson：JSON 序列化库</li>
<li>相似度匹配算法</li>
</ul>
<p>数据库：列出用于存储用户数据、队伍数据和博客数据的数据库系统，如 MySQL、MongoDB 等。</p>
<p>通信技术：列出用于实现即时聊天功能的通信技术，如 WebSocket、Socket.io 等。</p>
<h1 id="功能模块"><a href="#功能模块" class="headerlink" title="功能模块"></a>功能模块</h1><ul>
<li>用户认证模块<ul>
<li>用户注册功能：包括用户的基本信息和账户创建。</li>
<li>用户登录功能：验证用户提供的用户名和密码。</li>
<li>用户权限管理：根据用户角色和权限限制用户的操作范围。</li>
</ul>
</li>
<li>队伍管理模块<ul>
<li>创建队伍功能：允许用户创建新的队伍，并添加队伍的相关信息。</li>
<li>加入队伍功能：允许用户查找并加入现有的队伍。</li>
</ul>
</li>
<li>好友管理模块<ul>
<li>查找好友功能：用户可以查找其他用户并添加为好友。</li>
<li>好友聊天功能：提供好友之间的即时文本聊天功能。</li>
</ul>
</li>
<li>队内聊天模块<ul>
<li>队伍内部聊天功能：允许队伍成员之间进行实时文本交流。</li>
</ul>
</li>
<li>用户推荐模块<ul>
<li>标签匹配功能：根据用户的标签来为用户推荐与他相似度最高的其他用户。</li>
</ul>
</li>
<li>博客模块（日后增加）<ul>
<li>发布博客功能：用户可以发布自己的博客。</li>
<li>点赞、收藏功能：用户可以对博客进行点赞和收藏。</li>
<li>博客评论功能：用户可以对博客进行评论。</li>
</ul>
</li>
</ul>
<h2 id="用户模块"><a href="#用户模块" class="headerlink" title="用户模块"></a>用户模块</h2><h3 id="用户登录-注册"><a href="#用户登录-注册" class="headerlink" title="用户登录/注册"></a>用户登录/注册</h3><h3 id="查看用户信息"><a href="#查看用户信息" class="headerlink" title="查看用户信息"></a>查看用户信息</h3><h3 id="添加好友"><a href="#添加好友" class="headerlink" title="添加好友"></a>添加好友</h3><ul>
<li>添加好友后，数据库应该同时保存两条记录：我是你的好友，你是我的好友</li>
</ul>
<h3 id="查看在线好友信息"><a href="#查看在线好友信息" class="headerlink" title="查看在线好友信息"></a>查看在线好友信息</h3><ul>
<li>查看在线好友列表<ul>
<li>列表展示在线好友信息：昵称、头像、是否在线</li>
<li>如果不在线，显示最近在线时间（或显示多久前在线：X 天前、X 小时前、X 个月前）</li>
<li>可点击私聊</li>
<li>提供修改好友备注功能</li>
</ul>
</li>
</ul>
<h3 id="好友私聊"><a href="#好友私聊" class="headerlink" title="好友私聊"></a>好友私聊</h3><ul>
<li><p>如何实现好友间私聊？</p>
</li>
<li><p>这其中要思考的场景有：发送消息、接收消息、查看消息</p>
</li>
<li><p>在前后端搭建好 socket 环境、<strong>前端能够发送消息，服务端能正常转发消息到指定用户 id</strong>的前提下，我们考虑如何实现好友间私聊</p>
</li>
<li><h5 id="即：用户间通信"><a href="#即：用户间通信" class="headerlink" title="即：用户间通信"></a>即：用户间通信</h5><ul>
<li>用户发送消息（<strong>message</strong>），消息中携带的内容：发送者 id，目标 id，消息内容，发送时间</li>
<li>服务器收到消息，按以发送者 id 为 key 值，将 message 存储至 Redis 中，再转发 message 到目标用户</li>
<li>目标用户在线时，可以立刻获取到发来的 message，如若离线时，则需查看 Redis 中的存放的消息</li>
</ul>
</li>
<li><p>本来想着服务器如何给指定用户 sid 转发消息，好像不太好实现</p>
</li>
<li><p>在客户端尝试连接服务端 socket，并成功连接后，服务端的 webSocketSet 存放了 在线连接用户的信息</p>
</li>
<li><p>按现在的情况，服务端只能校验该消息是哪个用户所发的，即校验 webSocketSet 中存放的 sid，能否在已连接用户中找到发送消息的用户，并选择转发消息</p>
</li>
<li><h5 id="那只好是服务器同意转发至客户端，客户端根据-sendId-和-receiveId-选择是否展示"><a href="#那只好是服务器同意转发至客户端，客户端根据-sendId-和-receiveId-选择是否展示" class="headerlink" title="那只好是服务器同意转发至客户端，客户端根据 sendId 和 receiveId 选择是否展示"></a>那只好是服务器同意转发至客户端，客户端根据 sendId 和 receiveId 选择是否展示</h5></li>
</ul>
<h3 id="用户列表展示"><a href="#用户列表展示" class="headerlink" title="用户列表展示"></a>用户列表展示</h3><ul>
<li>用户不应该直接看到用户列表，用户可以添加好友，而添加好友的途径有三个：<ul>
<li>通过每日推荐，相似度匹配算法，每周给用户推荐兴趣爱好相似的用户，可以添加为好友畅聊</li>
<li>通过博文，在博客中，用户可以自由发表博文，对博主感兴趣即可申请添加好友</li>
<li>在搜索界面，根据不同的搜索参数：userAccoount，tags（用户标签），搜索到条件匹配的匹配用户</li>
</ul>
</li>
</ul>
<h2 id="队伍模块"><a href="#队伍模块" class="headerlink" title="队伍模块"></a>队伍模块</h2><h3 id="队伍创建问题"><a href="#队伍创建问题" class="headerlink" title="队伍创建问题"></a>队伍创建问题</h3><ul>
<li>用户如何创建队伍，应该有以下场景：<ul>
<li>在大厅发起组队，用户即可在组队大厅搜索到组队队伍（目前只想到这个方式）</li>
<li>创建队伍后，队长可以进行的操作：<ul>
<li>修改队伍信息</li>
<li>退出队伍、解散队伍</li>
<li>分享队伍，可以向好友发送组队邀请</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="队伍加入问题"><a href="#队伍加入问题" class="headerlink" title="队伍加入问题"></a>队伍加入问题</h3><h4 id="私密队伍如何加入？目前想到的场景："><a href="#私密队伍如何加入？目前想到的场景：" class="headerlink" title="私密队伍如何加入？目前想到的场景："></a>私密队伍如何加入？目前想到的场景：</h4><ul>
<li><p>当用户创建私密队伍后，其他用户将无法直接在大厅中看到该队伍，并申请加入。私密队伍的加入场景可以设计如下：</p>
<ul>
<li><p>邀请制加入：队长可以通过私信、邮件或其他方式向指定的用户发送邀请，邀请他们加入私密队伍。通过邀请链接或者邀请码，被邀请用户可以直接加入队伍。</p>
</li>
<li><p>好友推荐：队长可以在队伍详情中选择通过好友推荐的方式，将私密队伍信息分享给指定好友。被推荐用户可以看到私密队伍，并有选项选择是否加入。</p>
</li>
<li><p>特殊条件加入：队长可以设置一些特殊的条件，供用户满足后才能加入私密队伍。例如，要求用户达到一定的等级、完成特定任务或通过一定的测试等。</p>
</li>
</ul>
</li>
<li><p>在这些场景下，你可以使用数据库来存储队伍的私密信息，例如邀请码或特殊条件。当用户申请加入私密队伍时，你可以将用户输入的邀请码或满足的特殊条件与数据库中存储的信息进行匹配验证。如果验证通过，用户就可以成功加入私密队伍。</p>
</li>
<li><p>申请加入队伍：</p>
<ul>
<li>点击队伍标签，可展示更多信息（抽屉实现），可申请加入队伍</li>
<li>传入参数未：该队伍信息，后台处理申请请求，处理逻辑如下<ul>
<li>该队伍未满员</li>
<li>该成员不再该队伍中</li>
<li>更新队伍已加入人数</li>
<li>更新 user-team 表关系，添加记录</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="队伍退出"><a href="#队伍退出" class="headerlink" title="队伍退出"></a>队伍退出</h3><h3 id="队内私聊"><a href="#队内私聊" class="headerlink" title="队内私聊"></a>队内私聊</h3><h3 id="队伍推荐"><a href="#队伍推荐" class="headerlink" title="队伍推荐"></a>队伍推荐</h3><ul>
<li>即队伍成员可分享该队伍的信息，加大队伍曝光率</li>
</ul>
<h2 id="博客模块"><a href="#博客模块" class="headerlink" title="博客模块"></a>博客模块</h2><ul>
<li>这个网站首页就应该是<strong>博客模块</strong></li>
<li>创建博文表</li>
</ul>
<h3 id="博文展示"><a href="#博文展示" class="headerlink" title="博文展示"></a>博文展示</h3><ul>
<li><p>可筛选最新博文、最热门博文、</p>
</li>
<li><p>展示所有博文</p>
<ul>
<li>博文卡片展示信息：标题、简介、标签、发布时间、点赞量、收藏量、评论量、作者头像</li>
<li>点击进入博文详情页：与博文卡片展示内容相同，另附博文详细内容</li>
</ul>
</li>
</ul>
<h3 id="发表博文"><a href="#发表博文" class="headerlink" title="发表博文"></a>发表博文</h3><ul>
<li>用户可在博文编辑页，编辑博文：<ul>
<li>标题、简介、标签、内容</li>
<li>点击发表，发表博文；点击存入草稿，存入草稿</li>
</ul>
</li>
</ul>
<h3 id="点赞博文"><a href="#点赞博文" class="headerlink" title="点赞博文"></a>点赞博文</h3><h3 id="收藏博文"><a href="#收藏博文" class="headerlink" title="收藏博文"></a>收藏博文</h3><h3 id="评论博文"><a href="#评论博文" class="headerlink" title="评论博文"></a>评论博文</h3><ul>
<li>核心</li>
</ul>
<h2 id="界面设计"><a href="#界面设计" class="headerlink" title="界面设计"></a>界面设计</h2><ul>
<li>用于展示项目各个功能模块的界面设计，包括页面布局、交互流程和 UI 元素等。</li>
<li>主页</li>
<li>用户注册页：实现用户注册</li>
<li>用户登录页：实现用户登录</li>
<li>用户列表页：展示在线用户列表</li>
<li>用户信息页：展示用户信息</li>
<li>队伍列表页：展示队伍列表</li>
<li>队内聊天</li>
<li>好友聊天</li>
<li>博客列表页：展示已发布的博客</li>
<li>个人博客页：个人发布的博客展示</li>
</ul>
<h2 id="开发计划"><a href="#开发计划" class="headerlink" title="开发计划"></a>开发计划</h2><ul>
<li>列出项目开发的计划和里程碑，包括每个功能模块的开发时间和测试时间。</li>
</ul>
<h3 id="Day1"><a href="#Day1" class="headerlink" title="Day1"></a>Day1</h3><ul>
<li>实现主页页面的简单开发 的 ✔ <strong>（2023/09/11 晚）</strong></li>
<li>在线用户列表页、队伍信息页 ✔</li>
</ul>
<h3 id="Day2"><a href="#Day2" class="headerlink" title="Day2"></a>Day2</h3><ul>
<li><p>用户登录页</p>
<ul>
<li>简单的实现了登录页的开发，用户需输入账户密码进行登录，这样作双向绑定：✔ <strong>（2023/09/10 早）</strong></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-form-item<br>  label=&quot;用户名&quot;<br>  name=&quot;username&quot;<br>  :rules=&quot;[&#123; required: true, message: &#x27;请输入用户名&#x27; &#125;]&quot;<br>&gt;<br>          &lt;a-input :placeholder=&quot;&#x27;请输入用户名&#x27;&quot; v-model:value=&quot;userAccount&quot;/&gt;<br>        &lt;/a-form-item&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">const</span> userAccount = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&quot;&quot;</span>);<br></code></pre></td></tr></table></figure>

<ul>
<li><strong>登录成功之后跳转至主页</strong></li>
</ul>
</li>
<li><p>优化了主页页面显示</p>
<ul>
<li>通过监听 tab 标签页的变化，发起相应的请求 ✔</li>
<li>用户登录后，展示头像、昵称； ✔</li>
<li>用户未登录，提示登录，可点击按钮跳转至登录页面 ✔</li>
</ul>
</li>
<li><p>新增展示所有队伍功能，展示队伍列表 ✔</p>
<ul>
<li>默认仅展示公开队伍，可开启展示加密队伍 ✔</li>
<li>前端监听开关状态，发送<strong>带参（isSecret）请求</strong>，是否需要获取加密队伍： ✔</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 开关 队伍状态改变</span><br><span class="hljs-keyword">const</span> checked = ref&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);<br><span class="hljs-comment">// 监听开关状态变化</span><br><span class="hljs-title function_">watchEffect</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>  <span class="hljs-keyword">if</span> (checked.<span class="hljs-property">value</span>) &#123;<br>    <span class="hljs-title function_">getTeamList</span>(<span class="hljs-literal">true</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-title function_">getTeamList</span>(<span class="hljs-literal">false</span>);<br>  &#125;<br>&#125;);<br></code></pre></td></tr></table></figure>

<ul>
<li>后台校验逻辑</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (!teamList.getIsSecret()) &#123;<br>           tqw.eq(<span class="hljs-string">&quot;status&quot;</span>, TeamStatusEnum.PUBLIC.getValue());<br>       &#125; <span class="hljs-keyword">else</span> &#123;<br>           tqw.eq(<span class="hljs-string">&quot;status&quot;</span>, TeamStatusEnum.PUBLIC.getValue())<br>                   .or().eq(<span class="hljs-string">&quot;status&quot;</span>, TeamStatusEnum.SECRET.getValue());<br>       &#125;<br></code></pre></td></tr></table></figure></li>
<li><h5 id="队伍列表页面应该展示的信息有："><a href="#队伍列表页面应该展示的信息有：" class="headerlink" title="队伍列表页面应该展示的信息有："></a>队伍列表页面应该展示的信息有：</h5><ul>
<li>队伍名、队伍描述、队伍名片（牌面）✔</li>
<li>队伍已加入人数，可供用户判断是否满员（使用进度条实现），若满员，则显示已满员（爆红）✂</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;div&gt;<br>      &lt;a-slider id=&quot;test&quot; v-model:value=&quot;item.joinNum&quot; :max=&quot;item.maxNum&quot; disabled/&gt;<br>      &lt;span&gt;&#123;&#123;item.joinNum&#125;&#125;/&#123;&#123;item.maxNum&#125;&#125;&lt;/span&gt;<br> &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>加标签，显示该用户是否已加入该队伍；<ul>
<li>没加入，<strong>可加入</strong></li>
<li>已加入，<strong>可进入队伍聊天</strong></li>
</ul>
</li>
</ul>
</li>
<li><h5 id="队伍卡片可点击进入，队伍信息展示页：（使用抽屉组件实现）"><a href="#队伍卡片可点击进入，队伍信息展示页：（使用抽屉组件实现）" class="headerlink" title="队伍卡片可点击进入，队伍信息展示页：（使用抽屉组件实现）"></a>队伍卡片可点击进入，队伍信息展示页：（使用抽屉组件实现）</h5><ul>
<li><a target="_blank" rel="noopener" href="https://2x.antdv.com/components/drawer-cn">Ant Design Vue (antdv.com)</a></li>
<li>队伍名、队伍描述 ✔</li>
<li>队伍已加入成员的头像排列（使用头像组件实现）、队伍已加入人数 ✔</li>
</ul>
</li>
</ul>
<h4 id="问题解决"><a href="#问题解决" class="headerlink" title="问题解决"></a>问题解决</h4><ul>
<li>如何实现：点击队伍卡片，从右侧弹出抽屉，展示该队伍的详细信息</li>
<li>进行如下绑定（<strong>每个 card 绑定一个 drawer</strong>）<strong>（2023/9/10 晚）</strong></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-list-item&gt;<br>            &lt;a-card hoverable class=&quot;teamInfoCard&quot; @click=&quot;showDrawer(item.id)&quot;&gt;<br>              &lt;template #cover&gt;<br>                &lt;img alt=&quot;example&quot; :src=&quot;item.imgUrl&quot;/&gt;<br>              &lt;/template&gt;<br><br>              &lt;a-card-meta :title=&quot;item.name&quot;&gt;<br>                &lt;template #description&gt;&#123;&#123; item.description &#125;&#125;&lt;/template&gt;<br>              &lt;/a-card-meta&gt;<br>            &lt;/a-card&gt;<br><br>            &lt;a-drawer<br>                :key=&quot;item.id&quot;<br>                :title=&quot;item.name&quot;<br>                placement=&quot;right&quot;<br>                :closable=&quot;false&quot;<br>                v-model:visible=&quot;visible[item.id]&quot;<br>                :after-visible-change=&quot;afterVisibleChange&quot;<br>            &gt;<br>            &lt;/a-drawer&gt;<br>          &lt;/a-list-item&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 抽屉展示</span><br><span class="hljs-keyword">const</span> visible = <span class="hljs-title function_">ref</span>(&#123;&#125;);<br><span class="hljs-keyword">const</span> <span class="hljs-title function_">showDrawer</span> = (<span class="hljs-params">itemId: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  visible.<span class="hljs-property">value</span>[itemId] = <span class="hljs-literal">true</span>;<br>&#125;;<br></code></pre></td></tr></table></figure>

<h3 id="Day3"><a href="#Day3" class="headerlink" title="Day3"></a>Day3</h3><ul>
<li>实现用户间互相添加好友，可在“我的好友”中查看 <strong>（2023/09/11 晚）</strong></li>
<li>用户申请加入队伍，可在“我的队伍”中查看</li>
<li>用户创建队伍、退出队伍</li>
<li>在线用户列表中，排除当前用户：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">QueryWrapper&lt;User&gt; lqw = <span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryWrapper</span>&lt;&gt;();<br>      lqw.ne(<span class="hljs-string">&quot;id&quot;</span>, loginUser.getId());<br></code></pre></td></tr></table></figure>

<ul>
<li><p>添加用户信息标签展示</p>
<ul>
<li>点击可展示更多信息（抽屉组件实现）</li>
<li>可申请添加为好友<ul>
<li>申请添加好友时，请求参数为该好友的 id，传回后端将登录用户和好友 id，好友表新增一条记录</li>
</ul>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 申请添加好友</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">addFriends</span> = (<span class="hljs-params">friend: userInfo</span>) =&gt; &#123;<br>  myAxios<br>    .<span class="hljs-title function_">post</span>(<span class="hljs-string">&quot;/friends/add&quot;</span>, &#123;<br>      <span class="hljs-attr">id</span>: friend.<span class="hljs-property">id</span>,<br>    &#125;)<br>    .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>      message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功发送好友申请&quot;</span>);<br>    &#125;)<br>    .<span class="hljs-title function_">catch</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;加入失败&quot;</span>);<br>    &#125;);<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="可与该队友聊天"><a href="#可与该队友聊天" class="headerlink" title="可与该队友聊天"></a>可与该队友聊天</h5></li>
</ul>
</li>
<li><p>展示我的好友列表</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">fqw.select(<span class="hljs-string">&quot;friend_id&quot;</span>);<br>List&lt;Friends&gt; friendsList = list(fqw);<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">根据id查询道好友信息<br>   <span class="hljs-keyword">for</span> (Friends friends : friendsList) &#123;<br>            <span class="hljs-type">Long</span> <span class="hljs-variable">friendId</span> <span class="hljs-operator">=</span> friends.getFriendId();<br>            uqw.eq(<span class="hljs-string">&quot;id&quot;</span>, friendId);<br><br>            <span class="hljs-type">User</span> <span class="hljs-variable">one</span> <span class="hljs-operator">=</span> userService.getOne(uqw);<br>            userList.add(one);<br>        &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="这里添加-fqw-select-“friend-id”-后，friend-为-null，待解决"><a href="#这里添加-fqw-select-“friend-id”-后，friend-为-null，待解决" class="headerlink" title="这里添加 fqw.select(“friend_id”); 后，friend 为 null，待解决"></a>这里添加 fqw.select(“friend_id”); 后，friend 为 null，待解决</h5></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> friends<br>(<br>    id          <span class="hljs-type">bigint</span> auto_increment comment <span class="hljs-string">&#x27;id&#x27;</span><br>        <span class="hljs-keyword">primary</span> key,<br>    user_id     <span class="hljs-type">bigint</span>                               <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;用户id&#x27;</span>,<br>    friend_id   <span class="hljs-type">bigint</span>                               <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;好友id&#x27;</span>,<br>    note        <span class="hljs-type">varchar</span>(<span class="hljs-number">8</span>)                           <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;好友备注&#x27;</span>,<br>    create_time datetime   <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;创建时间&#x27;</span>,<br>    update_time datetime   <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">null</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">update</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span>,<br>    is_delete   tinyint(<span class="hljs-number">1</span>) <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;是否删除&#x27;</span><br>)<br>    comment <span class="hljs-string">&#x27;好友表&#x27;</span>;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>添加好友信息逻辑：</p>
<ul>
<li>传递参数：该好友信息、申请用户信息</li>
<li>该用户好友数未上限（<strong>目前没有上限，日后优化</strong>）</li>
<li>更新好友表，添加一条记录</li>
</ul>
</li>
<li><p>优化队伍列表展示</p>
<ul>
<li>目前还没有实现搜索队伍、搜索用户功能，展示队伍列表逻辑如下：<ul>
<li>默认展示公开队伍，可选择展示加密队伍，大厅不支持用户直接加入私密队伍</li>
<li>显示已加入的队伍，排列顺序考前，不允许用户加入该队伍</li>
<li>加入加密队伍需要输入密码，加入公开队伍则无</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="Day4"><a href="#Day4" class="headerlink" title="Day4"></a>Day4</h3><ul>
<li><p>在博客系统开发前，可以考虑将后端代码分模块</p>
</li>
<li><h5 id="添加用户登录状态字段（2023-09-12-早）"><a href="#添加用户登录状态字段（2023-09-12-早）" class="headerlink" title="添加用户登录状态字段（2023/09/12 早）"></a>添加用户登录状态字段（2023/09/12 早）</h5></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">is_online     <span class="hljs-type">int</span>                                    <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;是否在线&#x27;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="优化登录-退出登录体验"><a href="#优化登录-退出登录体验" class="headerlink" title="优化登录/退出登录体验"></a>优化登录/退出登录体验</h5><ul>
<li><p>登录后</p>
<ul>
<li>后端，存储用户信息到 session 中，设置用户状态为在线（<strong>ONLINE</strong>），并返回该信息到前端</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);<br></code></pre></td></tr></table></figure>

<ul>
<li>前端：前端存储登录用户信息到变量 currentUser 中，用于其他业务逻辑中的校验当前登录用户权限</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">const</span> currentUser = <span class="hljs-title function_">ref</span>(&#123;&#125;);<br></code></pre></td></tr></table></figure></li>
<li><p>登出后</p>
<ul>
<li>后端，删除 session 中的登录用户信息，设置用户状态为在线（<strong>OFFLINE</strong>）</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">request.getSession().removeAttribute(USER_LOGIN_STATE);<br></code></pre></td></tr></table></figure>

<ul>
<li>前端，currentUser 置为空</li>
</ul>
</li>
</ul>
</li>
<li><p>好友在线列表，展示好友的在线状况</p>
</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;div v-if=&quot;item.isOnline === 1&quot;&gt;<br>                &lt;a-tag color=&quot;processing&quot; &gt;<br>                  在线<br>                &lt;/a-tag&gt;<br>              &lt;/div&gt;<br><br>&lt;div v-if=&quot;item.isOnline === 0&quot;&gt;<br>                &lt;a-tag color=&quot;default&quot; &gt;<br>                  下线<br>                &lt;/a-tag&gt;<br>              &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>想解决同一个浏览器只能登录一个账号的问题，但这个问题解决不了的，看看 AI 的解释：</li>
</ul>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs pgsql"><br>存储登录用户信息在 <span class="hljs-keyword">session</span> 中，通常是基于会话的状态管理机制。而一个浏览器不能同时登录两个用户的主要原因是浏览器的 Cookie 机制和同一域名下的会话管理。<br>当一个用户登录后，服务器会在浏览器中设置一个名为 <span class="hljs-keyword">Session</span> ID 的 Cookie，用于识别会话。浏览器每次发送请求时，会自动将该 Cookie 值包括在请求头中，服务器会根据该 Cookie 值找到对应的会话信息。<br>由于浏览器默认只能存储一个名为 <span class="hljs-keyword">Session</span> ID 的 Cookie，所以同一浏览器只能保持一个登录会话。当再次登录另一个用户时，服务器会更新 <span class="hljs-keyword">Session</span> ID，并将新的 Cookie 值响应给浏览器，导致之前的会话信息失效。<br>即使使用 Redis 存储会话数据，也无法解决一个浏览器不能同时登录两个用户的问题。因为是浏览器自身的限制，无论使用什么方式存储会话数据，都无法绕过浏览器同一域名同一时刻只能持有一个 Cookie 的特性。<br>如果需要在同一浏览器中同时登录多个用户，可以考虑使用不同的浏览器或使用浏览器的隐私模式或使用不同的域名或端口。或者可以使用多个浏览器会话、多个标签页或多个窗口的方式，在同一浏览器中模拟多个用户。<br>希望能够解答你的疑问，如果还有其他问题，请随时提问。<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="解决方案：使用不同的浏览器或者使用浏览器的隐私模式"><a href="#解决方案：使用不同的浏览器或者使用浏览器的隐私模式" class="headerlink" title="解决方案：使用不同的浏览器或者使用浏览器的隐私模式"></a>解决方案：使用不同的浏览器或者使用浏览器的隐私模式</h5></li>
<li><p>添加好友，同时添加两条记录：</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"> <span class="hljs-comment">// 他成为我的好友</span><br><span class="hljs-type">Friends</span> <span class="hljs-variable">friends</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Friends</span>();<br>friends.setUserId(userId);<br>friends.setFriendId(friend.getId());<br>friends.setNote(<span class="hljs-string">&quot;我的好友&quot;</span>);<br>friends.setCreateTime(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>friends.setUpdateTime(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>friends.setIsDelete(<span class="hljs-number">0</span>);<br><span class="hljs-type">boolean</span> <span class="hljs-variable">save1</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.save(friends);<br><br><span class="hljs-comment">// 我成为他的好友</span><br>friends = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Friends</span>();<br>friends.setUserId(friend.getId());<br>friends.setFriendId(userId);<br>friends.setNote(<span class="hljs-string">&quot;我的好友&quot;</span>);<br>friends.setCreateTime(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>friends.setUpdateTime(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>friends.setIsDelete(<span class="hljs-number">0</span>);<br><span class="hljs-type">boolean</span> <span class="hljs-variable">save2</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.save(friends);<br><br><span class="hljs-comment">// 保证原子操作</span><br><span class="hljs-keyword">if</span> (!save1 || !save2) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.UPDATE_ERROR, <span class="hljs-string">&quot;建立好友关系失败&quot;</span>);<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="较冗长，待优化"><a href="#较冗长，待优化" class="headerlink" title="较冗长，待优化"></a>较冗长，待优化</h5></li>
<li><p>完成加入队伍逻辑实现</p>
</li>
<li><p>展示我创建的队伍、我加入的队伍</p>
<ul>
<li>要明确，我创建的队伍，team 表中保存有队长 userId 字段，仅需在 team 表中，根据 userId 字段，直接查询出对应记录的 team 信息</li>
<li>我加入的队伍，在 user_team 表中，根据 userId，查询到对应的 teamId，在 team 表中，根据 teamId 获取到 team 信息</li>
</ul>
</li>
<li><p>后台返回的队伍信息，应该包括<strong>队长信息</strong>，而不是<strong>队长 id</strong>（待完成）</p>
<ul>
<li>创建 teamVO，封装参数 userName，即队长昵称</li>
<li>我封装了一个方法，传入一个 teamList，将所有的 team 转换为 teamVO，最后返回：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">   * 转换teamList为teamVOList</span><br><span class="hljs-comment">   *</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> teamList teamList</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@return</span> teamVOList</span><br><span class="hljs-comment">   */</span><br>  <span class="hljs-keyword">public</span> List&lt;TeamVO&gt; <span class="hljs-title function_">getTeamVOByTeam</span><span class="hljs-params">(List&lt;Team&gt; teamList)</span> &#123;<br>      List&lt;TeamVO&gt; teamVOList = teamList.stream().map(team -&gt; &#123;<br>          <span class="hljs-type">Long</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> team.getUserId();<br>          <span class="hljs-type">String</span> <span class="hljs-variable">userName</span> <span class="hljs-operator">=</span> userService.getById(userId).getUsername();<br>          <span class="hljs-type">TeamVO</span> <span class="hljs-variable">teamVO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TeamVO</span>();<br><br>          teamVO.setId(team.getId());<br>          teamVO.setName(team.getName());<br>          teamVO.setDescription(team.getDescription());<br>          teamVO.setMaxNum(team.getMaxNum());<br>          teamVO.setUserName(userName);<br>          teamVO.setImgUrl(team.getImgUrl());<br>          teamVO.setJoinNum(team.getJoinNum());<br>          teamVO.setStatus(team.getStatus());<br>          teamVO.setExpireTime(team.getExpireTime());<br>          teamVO.setCreateTime(team.getCreateTime());<br>          teamVO.setUpdateTime(team.getUpdateTime());<br>          teamVO.setIsDelete(team.getIsDelete());<br><br>          <span class="hljs-keyword">return</span> teamVO;<br>      &#125;).collect(Collectors.toList());<br><br>      <span class="hljs-keyword">return</span> teamVOList;<br>  &#125;<br></code></pre></td></tr></table></figure></li>
</ul>
<h3 id="Day5"><a href="#Day5" class="headerlink" title="Day5"></a>Day5</h3><ul>
<li><p>实现用户私聊，相关配置我已做好，详情可参考这篇博客：<a target="_blank" rel="noopener" href="https://blog.csdn.net/u011974797/article/details/130055062">SpringBoot 集成 WebSocket 进行前后端通信_谢小涛的博客-CSDN 博客</a></p>
</li>
<li><h5 id="后续该功能完成后，会详细记录-（2023-09-13-早）"><a href="#后续该功能完成后，会详细记录-（2023-09-13-早）" class="headerlink" title="后续该功能完成后，会详细记录 （2023/09/13 早）"></a>后续该功能完成后，会详细记录 （2023/09/13 早）</h5></li>
<li><p>点击进入私聊，展示聊天信息</p>
</li>
<li><p>对于聊天信息的展示，还有几分疑惑：</p>
<ul>
<li>用户发送消息后，服务器转发给指定用户应该怎样实现？</li>
<li>还是说存储到 Redis 中后，用户在私聊的聊天页面中，直接从 Redis 中查找自己与对方的消息，再展示出来呢？</li>
<li>这些问题我还感到很迷惑，一时想不明白如何实现，待解决</li>
</ul>
</li>
<li><p>现在待解决的问题还很多：</p>
<ul>
<li><p>用户点击聊天用户，进入聊天页面，这个页面下，能够获取到双方的聊天记录</p>
</li>
<li><p>发送消息，就是在添加记录，并实时展示的过程，</p>
</li>
<li><h5 id="页面的设计我有很多想法，通信功能实现，待解决"><a href="#页面的设计我有很多想法，通信功能实现，待解决" class="headerlink" title="页面的设计我有很多想法，通信功能实现，待解决"></a>页面的设计我有很多想法，通信功能实现，待解决</h5></li>
</ul>
</li>
</ul>
<h3 id="Day6"><a href="#Day6" class="headerlink" title="Day6"></a>Day6</h3><ul>
<li><h5 id="成功获取到存储在-Redis-中的信息（2023-09-14-早）"><a href="#成功获取到存储在-Redis-中的信息（2023-09-14-早）" class="headerlink" title="成功获取到存储在 Redis 中的信息（2023/09/14 早）"></a>成功获取到存储在 Redis 中的信息（2023/09/14 早）</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">msgJson</span> <span class="hljs-operator">=</span> (String) redisTemplate.opsForValue().get(<span class="hljs-string">&quot;memory:user:message:&quot;</span> + loginUserId);<br>        <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>        <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> gson.fromJson(msgJson, Message.class);<br>        <span class="hljs-keyword">if</span> (message == <span class="hljs-literal">null</span>)<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br><br>        <span class="hljs-keyword">return</span> message;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="用户发送消息：以用户-id-为-key，选用-Hash-数据结构，存储发出的各条消息："><a href="#用户发送消息：以用户-id-为-key，选用-Hash-数据结构，存储发出的各条消息：" class="headerlink" title="用户发送消息：以用户 id 为 key，选用 Hash 数据结构，存储发出的各条消息："></a>用户发送消息：以用户 id 为 key，选用 Hash 数据结构，存储发出的各条消息：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">HashOperations&lt;String, Object, Object&gt; opsForHash = redisTemplate.opsForHash();<br>      opsForHash.put(senderMsgKey, generateMessageId(), message);<br>      opsForHash.put(receiverMsgKey, generateMessageId(), message);<br>      <span class="hljs-comment">// 3.3.设置键的过期时间，单位为h</span><br>      <span class="hljs-type">long</span> <span class="hljs-variable">expireTime</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>; <span class="hljs-comment">// 设置为2hour</span><br>      redisTemplate.expire(senderMsgKey, expireTime, TimeUnit.HOURS);<br>      redisTemplate.expire(receiverMsgKey, expireTime, TimeUnit.HOURS);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="获取消息记录：以当前用户-id-为-key，获取存储的消息列表，返回给前端"><a href="#获取消息记录：以当前用户-id-为-key，获取存储的消息列表，返回给前端" class="headerlink" title="获取消息记录：以当前用户 id 为 key，获取存储的消息列表，返回给前端"></a>获取消息记录：以当前用户 id 为 key，获取存储的消息列表，返回给前端</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;Object, Object&gt; mesEntriesJson = redisTemplate.opsForHash().entries(USER_CHAT_MESSAGE + loginUserId);<br>      <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>      <span class="hljs-type">String</span> <span class="hljs-variable">jsonString</span> <span class="hljs-operator">=</span> gson.toJson(mesEntriesJson);<br>      Map&lt;Object, Object&gt; mesEntries = gson.fromJson(jsonString, <span class="hljs-keyword">new</span> <span class="hljs-title class_">TypeToken</span>&lt;Map&lt;Object, Object&gt;&gt;() &#123;<br>      &#125;.getType());<br><br>      <span class="hljs-keyword">if</span> (mesEntries == <span class="hljs-literal">null</span>)<br>          <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br><br>      ArrayList&lt;Message&gt; messageList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>      Collection&lt;Object&gt; mesValuesJson = mesEntries.values();<br>      <span class="hljs-keyword">for</span> (Object mesValueJson : mesValuesJson) &#123;<br>          <span class="hljs-type">String</span> <span class="hljs-variable">mesValuesJsonStr</span> <span class="hljs-operator">=</span> (String) mesValueJson;<br>          <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> gson.fromJson(mesValuesJsonStr, Message.class);<br>          messageList.add(message);<br>      &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>如何实现在用户聊天过程中，实时更新聊天记录内容呢？需解决两个问题：</p>
<ul>
<li>用户发送消息后，立刻展示在页面（发送消息给服务器，服务器存储信息到 redis 后，再立刻查询该消息，并返回到前台）</li>
<li>对方发送消息后，立刻展示在页面<ul>
<li>这个实现还在思考中，需要前端不断查询 redis，消息发生被更新就立刻同步到前台吗？太不现实了，很浪费资源</li>
<li>那就应该是服务器主动转发消息了</li>
</ul>
</li>
</ul>
</li>
<li><p>本来想着服务器如何给指定用户 sid 转发消息，好像不太好实现</p>
</li>
<li><p>在客户端尝试连接服务端 socket，并成功连接后，服务端的 webSocketSet 存放了 在线连接用户的信息</p>
</li>
<li><p>按现在的情况，服务端只能校验该消息是哪个用户所发的，即校验 webSocketSet 中存放的 sid，能否在已连接用户中找到发送消息的用户，并选择转发消息</p>
</li>
<li><h5 id="那只好是服务器同意转发至客户端，客户端根据-sendId-和-receiveId-选择是否展示-1"><a href="#那只好是服务器同意转发至客户端，客户端根据-sendId-和-receiveId-选择是否展示-1" class="headerlink" title="那只好是服务器同意转发至客户端，客户端根据 sendId 和 receiveId 选择是否展示"></a>那只好是服务器同意转发至客户端，客户端根据 sendId 和 receiveId 选择是否展示</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 接受客户端发来的消息</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> message 消息</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@throws</span> IOException IOException</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@OnMessage</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(String message)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>    log.info(<span class="hljs-string">&quot;收到来自窗口&quot;</span> + sid + <span class="hljs-string">&quot;的信息:&quot;</span> + message);<br>    <span class="hljs-comment">// 1.消息存放Redis</span><br>    <span class="hljs-type">boolean</span> <span class="hljs-variable">saveMessage</span> <span class="hljs-operator">=</span> saveMessage(message);<br>    <span class="hljs-comment">// 2.校验存放</span><br>    <span class="hljs-keyword">if</span> (!saveMessage) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.UPDATE_ERROR_REDIS, <span class="hljs-string">&quot;用户信息存放失败&quot;</span>);<br>    &#125;<br>    <span class="hljs-comment">// 3.消息转发</span><br>    msgForward(message, webSocketSet, sid);<br>&#125;<br></code></pre></td></tr></table></figure>

<h3 id="Day7"><a href="#Day7" class="headerlink" title="Day7"></a>Day7</h3><ul>
<li><p>实时双向通信终于测试成功，详情可见 <strong>踩坑记录</strong> 栏目的 **双向通信调试成功 ** <strong>（2023/09/15 早）</strong></p>
</li>
<li><p>完成用户私聊功能、完善组队功能，开发队伍聊天功能</p>
</li>
<li><p>计划优化整个网站的页面效果</p>
</li>
<li><p>点击好友私聊，跳转至聊天页面</p>
<ul>
<li>首先要开发一个聊天页面：聊天窗口、可支持输入聊天内容、点击发送</li>
<li>携带好友 id 进入聊天页面，确认消息的接收方</li>
<li>用户登录以后，就应该连接到服务器的 socket 服务了，这样既可以实时接收消息，也可以在后台接收消息、标注为未读</li>
</ul>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">//获得消息事件(获得服务端转发的消息)</span><br>socket.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">msg</span>) &#123;<br>  receiveMsg.<span class="hljs-property">value</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>);<br>  <span class="hljs-comment">// 是发给自己的消息 更新聊天记录</span><br>  <span class="hljs-keyword">if</span> (currentUserId === receiveMsg.<span class="hljs-property">value</span>.<span class="hljs-property">receiverId</span>) &#123;<br>    <span class="hljs-title function_">getMesList</span>();<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>用户能够发送消息给指定用户，实现了只有指定用户才能接收到该消息的功能</p>
</li>
<li><p>优化消息列表展示</p>
</li>
<li><p>Vue 前端页面实现真麻烦，难倒我了，本来获取当前页面路由很简单的，搞了半天。。</p>
</li>
</ul>
<h3 id="页面更新"><a href="#页面更新" class="headerlink" title="页面更新"></a>页面更新</h3><ul>
<li><p>经过一周的沉淀，想明白了整个网站的页面布局</p>
</li>
<li><p>点击私聊，可跳转至聊天页面</p>
<ul>
<li>本来打算这个功能是在 tab 页之间跳转的，但是 tab 组件仅支持监听 tab 页变化，获取 key 值，不支持根据 key 值，跳转 tab 页</li>
<li>索性直接把页面大改版，做成三个主页面：<ul>
<li>用户功能页（找用户、找队伍）</li>
<li>聊天页（<strong>私聊、队内聊天、大厅聊天</strong>）</li>
<li>博客页（博客展示、<strong>评论</strong>、点赞、收藏）</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230915183535885.png" alt="image-20230915183535885"></p>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230915183544621.png" alt="image-20230915183544621"></p>
<h3 id="Day8"><a href="#Day8" class="headerlink" title="Day8"></a>Day8</h3><h4 id="修改数据库"><a href="#修改数据库" class="headerlink" title="修改数据库"></a>修改数据库</h4><ul>
<li><p>这个账户和昵称我一直认为不合理</p>
<ul>
<li>统一为用户名 userAccount</li>
<li>用户注册/登录后支持修改个人信息：绑定电话、邮箱，上传头像，修改用户名和密码</li>
<li><h5 id="不过保留也可以，用户使用唯一账户登录后，如果没有取昵称，强制弹窗要求用户为自己取一个昵称，我真聪明"><a href="#不过保留也可以，用户使用唯一账户登录后，如果没有取昵称，强制弹窗要求用户为自己取一个昵称，我真聪明" class="headerlink" title="不过保留也可以，用户使用唯一账户登录后，如果没有取昵称，强制弹窗要求用户为自己取一个昵称，我真聪明"></a>不过保留也可以，用户使用唯一账户登录后，如果没有取昵称，强制弹窗要求用户为自己取一个昵称，我真聪明</h5></li>
</ul>
</li>
<li><p>好友列表 点击 <strong>私聊</strong> 跳转至聊天大厅的特定聊天窗口处<strong>（2023/09/16 早）</strong></p>
<ul>
<li>传递好友 id，聊天大厅 tab 页接收 id 参数</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 监听页面变化</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">goToTab</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  router.<span class="hljs-title function_">push</span>(&#123;<br>    <span class="hljs-attr">name</span>: <span class="hljs-string">&quot;chat&quot;</span>,<br>    <span class="hljs-attr">path</span>: <span class="hljs-string">&quot;/chat&quot;</span>,<br>    <span class="hljs-attr">query</span>: &#123;<br>      <span class="hljs-attr">chatUserId</span>: <span class="hljs-number">12345</span>,<br>    &#125;,<br>  &#125;);<br>&#125;;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">const</span> route = <span class="hljs-title function_">useRoute</span>();<br><span class="hljs-keyword">const</span> chatTabName = <span class="hljs-title function_">ref</span>(&#123;&#125;);<br><span class="hljs-comment">// 钩子函数</span><br><span class="hljs-title function_">onMounted</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>  chatTabName.<span class="hljs-property">value</span> = route.<span class="hljs-property">query</span>;<br>&#125;);<br></code></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-tab-pane key=&quot;1&quot; :tab=&quot;chatTabName.chatUserId&quot;&gt;<br></code></pre></td></tr></table></figure></li>
<li><p>原本打算做这样的实现：</p>
<ul>
<li>点击好友私聊后，能够跳转至聊天大厅，并创建一个<strong>特有的 tab 标签页</strong>，用来做与该好友的<strong>聊天页面</strong></li>
<li>只要传递好友<strong>username 作为参数</strong>，将 tab 页命名为 username，即可<strong>创建特有的 tab 标签页</strong></li>
<li>但是每次只能由一个好友聊天窗口，像这样：</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230916131802853.png" alt="image-20230916131802853"></p>
<ul>
<li>这是因为每次只能追加一个 tab 页面，而退出该页面后，添加的 tab 页组件就没了，不能保存下来</li>
<li>有关这样的问题，肯定是有解决办法的，好像有个 keep-alive 标签就是干这个的，不过不深究了</li>
</ul>
</li>
<li><p>现在索性改为这样实现了：</p>
<ul>
<li><strong>直接罗列出所有的好友聊天列表</strong>，每次点击私聊后不是<strong>根据传递的参数新增一个 tab 页</strong>，而是<strong>定位到对应的标签页</strong></li>
</ul>
</li>
<li><p>成功实现点击私聊后，跳转至聊天大厅中的对应聊天窗口</p>
</li>
<li><p>正式着手开发双向实时通信，还有个小 BUG，我自己的消息，会在每个聊天窗口显示</p>
</li>
<li><p>查找聊天消息时，应该在每个聊天窗口发出请求，请求参数为当前用户 id 和聊天对象 id，查询这些消息</p>
</li>
<li><p>业务逻辑好像出问题了：</p>
<ul>
<li>现在用户给指定用户发送消息后，确实保证了只有该用户能收到消息</li>
<li>但是收到消息如何持久化到聊天记录中呢？不能光打印一下就完事了对吧</li>
<li>那就是要重新查询该聊天窗口的消息，并返回给前端</li>
<li>怎么查询？根据两用户的 id 查询：<strong>你给我发的消息，我给你发的消息</strong></li>
</ul>
</li>
<li><h5 id="成功解决核心问题，基本实现了实时双向聊天通信功能-🌞🌞🌞🌞🌞"><a href="#成功解决核心问题，基本实现了实时双向聊天通信功能-🌞🌞🌞🌞🌞" class="headerlink" title="成功解决核心问题，基本实现了实时双向聊天通信功能 🌞🌞🌞🌞🌞"></a>成功解决核心问题，基本实现了实时双向聊天通信功能 🌞🌞🌞🌞🌞</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230916212327320.png" alt="image-20230916212327320"></p>
<p><img src="http://blog.memory-life.xyz/image-20230916212506289.png" alt="image-20230916212506289"></p>
<ul>
<li><h5 id="添加用户昵称或头像，在聊天中能分辨收发双方"><a href="#添加用户昵称或头像，在聊天中能分辨收发双方" class="headerlink" title="添加用户昵称或头像，在聊天中能分辨收发双方"></a>添加用户昵称或头像，在聊天中能分辨收发双方</h5></li>
</ul>
<h3 id="Day9"><a href="#Day9" class="headerlink" title="Day9"></a>Day9</h3><ul>
<li><h5 id="了解-Java-并发编程，批量插入用户数据（2023-09-17-晚）"><a href="#了解-Java-并发编程，批量插入用户数据（2023-09-17-晚）" class="headerlink" title="了解 Java 并发编程，批量插入用户数据（2023/09/17 晚）"></a>了解 Java 并发编程，批量插入用户数据（2023/09/17 晚）</h5></li>
<li><p>每周推荐用户：</p>
<ul>
<li>相似度匹配算法</li>
<li>根据用户标签，对比标签的相似度，得出相似度降序排行，取出前 5 条数据，即每周推荐用户为 5 个</li>
<li>如何实现每周推荐 5 个呢？写个定时任务，只要服务器开启，每隔一周查询一次相似用户，并更新到数据库中</li>
</ul>
</li>
<li><p>实现用户推荐，改为了这样的实现：</p>
<ul>
<li><p>获取推荐用户列表</p>
</li>
<li><h5 id="如果成功从缓存中获取，则返回；否则查询数据库，更新缓存，设置-7-天过期"><a href="#如果成功从缓存中获取，则返回；否则查询数据库，更新缓存，设置-7-天过期" class="headerlink" title="如果成功从缓存中获取，则返回；否则查询数据库，更新缓存，设置 7 天过期"></a>如果成功从缓存中获取，则返回；否则查询数据库，更新缓存，设置 7 天过期</h5></li>
<li><p>这样也巧妙的实现了每周推荐，不需要写定时任务了</p>
</li>
</ul>
</li>
<li><p>解决刷新 chatPage.vue 页后，当前页登录用户获取失败的问题</p>
</li>
<li><p>进入聊天大厅，自动连接服务器，提供双向实时通信服务</p>
</li>
<li><h5 id="计划开发博文分享页了，参考这个："><a href="#计划开发博文分享页了，参考这个：" class="headerlink" title="计划开发博文分享页了，参考这个："></a>计划开发博文分享页了，参考这个：</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230917230332243.png" alt="image-20230917230332243"></p>
<ul>
<li>思考设计博文表，同时优化通信功能<strong>（2023/09/17 晚）</strong></li>
</ul>
<h3 id="优化页面计划："><a href="#优化页面计划：" class="headerlink" title="优化页面计划："></a>优化页面计划：</h3><ul>
<li>页头，不使用 <strong>a-page-header 组件</strong>了，自己加个毛玻璃效果挺好看的</li>
<li><a target="_blank" rel="noopener" href="http://zhongguose.com/#luanshizi">卵石紫 - 中国色 - 中国传统颜色 (zhongguose.com)</a></li>
</ul>
<h3 id="Day10："><a href="#Day10：" class="headerlink" title="Day10："></a>Day10：</h3><ul>
<li><h5 id="博客表的创建：SQL-之父"><a href="#博客表的创建：SQL-之父" class="headerlink" title="博客表的创建：SQL 之父"></a>博客表的创建：SQL 之父</h5></li>
<li><p>博客列表展示：</p>
<ul>
<li>添加测试数据，设计简单的博客页展示</li>
</ul>
</li>
<li><p>优化每周推荐用户列表展示</p>
</li>
<li><p>优化实时双向通信体验</p>
</li>
<li><h5 id="用户未登录时，拿取推荐列表报错，需校验当前用户是否登录成功"><a href="#用户未登录时，拿取推荐列表报错，需校验当前用户是否登录成功" class="headerlink" title="用户未登录时，拿取推荐列表报错，需校验当前用户是否登录成功"></a>用户未登录时，拿取推荐列表报错，需校验当前用户是否登录成功</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 获取推荐用户</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">getMatchUserList</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  myAxios<br>    .<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;/user/match&quot;</span>, &#123;<br>      <span class="hljs-attr">params</span>: &#123;<br>        <span class="hljs-attr">matchNum</span>: matchNum.<span class="hljs-property">value</span>,<br>      &#125;,<br>    &#125;)<br>    .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>      <span class="hljs-keyword">if</span> (res.<span class="hljs-property">data</span> !== <span class="hljs-literal">null</span>) &#123;<br>        matchUserList.<span class="hljs-property">value</span> = res.<span class="hljs-property">data</span>.<span class="hljs-property">records</span>;<br>      &#125;<br>    &#125;);<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="未登录，获取的推荐用户即为空，设置下为空时的页面展示即可"><a href="#未登录，获取的推荐用户即为空，设置下为空时的页面展示即可" class="headerlink" title="未登录，获取的推荐用户即为空，设置下为空时的页面展示即可"></a>未登录，获取的推荐用户即为空，设置下为空时的页面展示即可</h5></li>
<li><h5 id="测试成功，获取所有博文列表："><a href="#测试成功，获取所有博文列表：" class="headerlink" title="测试成功，获取所有博文列表："></a>测试成功，获取所有博文列表：</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230918134952276.png" alt="image-20230918134952276"></p>
<ul>
<li><h5 id="解决了-icon-图标不显示的问题，原来还要安装相关依赖才可以（看官网）"><a href="#解决了-icon-图标不显示的问题，原来还要安装相关依赖才可以（看官网）" class="headerlink" title="解决了 icon 图标不显示的问题，原来还要安装相关依赖才可以（看官网）"></a>解决了 icon 图标不显示的问题，原来还要安装相关依赖才可以（看官网）</h5></li>
</ul>
<figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs coffeescript"><span class="hljs-built_in">npm</span> install --save @ant-design/icons-vue<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="成功完成了页头的优化（导航栏透明、简洁、大方，且提供界面之间的来回跳转），还添加了渐变的背景色，效果如下："><a href="#成功完成了页头的优化（导航栏透明、简洁、大方，且提供界面之间的来回跳转），还添加了渐变的背景色，效果如下：" class="headerlink" title="成功完成了页头的优化（导航栏透明、简洁、大方，且提供界面之间的来回跳转），还添加了渐变的背景色，效果如下："></a>成功完成了页头的优化（导航栏透明、简洁、大方，且提供界面之间的来回跳转），还添加了渐变的背景色，效果如下：</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230918195353981.png" alt="image-20230918195353981"></p>
<p><img src="http://blog.memory-life.xyz/image-20230918195402432.png" alt="image-20230918195402432"></p>
<p><img src="http://blog.memory-life.xyz/image-20230918195407229.png" alt="image-20230918195407229"></p>
<ul>
<li><h5 id="博文发布："><a href="#博文发布：" class="headerlink" title="博文发布："></a>博文发布：</h5><ul>
<li>用户可以在博文编辑页，编写博文，并发布</li>
</ul>
</li>
</ul>
<h3 id="Day11"><a href="#Day11" class="headerlink" title="Day11"></a>Day11</h3><ul>
<li><p>实现计算<strong>每周匹配用户的匹配度</strong></p>
<ul>
<li><h5 id="编辑距离算法计算编辑距离"><a href="#编辑距离算法计算编辑距离" class="headerlink" title="编辑距离算法计算编辑距离"></a>编辑距离算法计算编辑距离</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 计算编辑距离</span><br><span class="hljs-type">long</span> <span class="hljs-variable">distance</span> <span class="hljs-operator">=</span> AlgorithmUtils.minDistance(tagList, userTagList)<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="根据最大编辑距离和最小编辑距离，计算匹配度"><a href="#根据最大编辑距离和最小编辑距离，计算匹配度" class="headerlink" title="根据最大编辑距离和最小编辑距离，计算匹配度"></a>根据最大编辑距离和最小编辑距离，计算匹配度</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 计算匹配度</span><br><span class="hljs-type">double</span> <span class="hljs-variable">percentage</span> <span class="hljs-operator">=</span> getPercentage(distance);<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 计算匹配度</span><br><span class="hljs-comment">    *</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@param</span> distance 编辑距离</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@return</span> 匹配度</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getPercentage</span><span class="hljs-params">(<span class="hljs-type">long</span> distance)</span> &#123;<br>       <span class="hljs-comment">// 计算匹配度的百分比</span><br>       <span class="hljs-keyword">return</span> (<span class="hljs-number">1</span> - (distance - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE)) * <span class="hljs-number">100</span>;<br>   &#125;<br><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="封装-UserVO-类，添加匹配度-percentage-字段"><a href="#封装-UserVO-类，添加匹配度-percentage-字段" class="headerlink" title="封装 UserVO 类，添加匹配度 percentage 字段"></a>封装 UserVO 类，添加匹配度 percentage 字段</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserVO</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">User</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> percentage;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="userList-转换-userVOList"><a href="#userList-转换-userVOList" class="headerlink" title="userList 转换 userVOList"></a>userList 转换 userVOList</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">   * 转换 userList 为 userVOList</span><br><span class="hljs-comment">   *</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> userList userList</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@return</span> teamVOList</span><br><span class="hljs-comment">   */</span><br>  <span class="hljs-keyword">public</span> List&lt;UserVO&gt; <span class="hljs-title function_">getUserVOByUser</span><span class="hljs-params">(List&lt;User&gt; userList, List&lt;Double&gt; distanceList)</span> &#123;<br>      <span class="hljs-keyword">return</span> IntStream.range(<span class="hljs-number">0</span>, userList.size())<br>              .mapToObj(i -&gt; &#123;<br>                  <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> userList.get(i);<br>                  <span class="hljs-type">double</span> <span class="hljs-variable">distance</span> <span class="hljs-operator">=</span> distanceList.get(i);<br><br>                  <span class="hljs-type">UserVO</span> <span class="hljs-variable">userVO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserVO</span>();<br>                  userVO.setPercentage(distance);<br>                  userVO.setId(user.getId());<br>                  userVO.setUserAccount(user.getUserAccount());<br>                  userVO.setUsername(user.getUsername());<br>                  userVO.setUserPassword(user.getUserPassword());<br>                  userVO.setAvatarUrl(user.getAvatarUrl());<br>                  userVO.setGender(user.getGender());<br>                  userVO.setPhone(user.getPhone());<br>                  userVO.setEmail(user.getEmail());<br>                  userVO.setUserStatus(user.getUserStatus());<br>                  userVO.setIsOnline(user.getIsOnline());<br>                  userVO.setCreateTime(user.getCreateTime());<br>                  userVO.setUpdateTime(user.getUpdateTime());<br>                  userVO.setIsDelete(user.getIsDelete());<br>                  userVO.setUserRole(user.getUserRole());<br>                  userVO.setPlanetCode(user.getPlanetCode());<br>                  userVO.setTags(user.getTags());<br>                  userVO.setProfile(user.getProfile());<br><br>                  <span class="hljs-keyword">return</span> userVO;<br>              &#125;)<br>              .collect(Collectors.toList());<br>  &#125;<br></code></pre></td></tr></table></figure></li>
<li><h5 id="将匹配度百分比保留了小数点后两位："><a href="#将匹配度百分比保留了小数点后两位：" class="headerlink" title="将匹配度百分比保留了小数点后两位："></a>将匹配度百分比保留了小数点后两位：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 计算匹配度</span><br><span class="hljs-comment">    *</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@param</span> distance 编辑距离</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@return</span> 匹配度</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getPercentage</span><span class="hljs-params">(<span class="hljs-type">long</span> distance)</span> &#123;<br>       <span class="hljs-comment">// 计算匹配度的百分比</span><br>       <span class="hljs-type">double</span> <span class="hljs-variable">percentage</span> <span class="hljs-operator">=</span> (<span class="hljs-number">1</span> - (distance - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE)) * <span class="hljs-number">100</span>;<br>       <span class="hljs-comment">// 将 double 数据保留小数点后两位，并转换为字符串</span><br>       <span class="hljs-type">String</span> <span class="hljs-variable">formattedNumber</span> <span class="hljs-operator">=</span> String.format(<span class="hljs-string">&quot;%.2f&quot;</span>, percentage);<br>       System.out.println(formattedNumber);<br>       <span class="hljs-comment">// 将String转换为Double</span><br>       <span class="hljs-keyword">return</span> Double.parseDouble(formattedNumber);<br>   &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>已成功定位到相关问题：</p>
<ul>
<li>在聊天大厅刷新页面后，向后端发送获取聊天记录列表时，<strong>没有携带当前登录用户 id</strong>，<strong>即 senderId = null</strong></li>
<li>这里发现一个问题：<ul>
<li>之前的聊天页面，只能通过点击私聊才能跳转，而跳转时，路由中是携带了聊天对象的 id</li>
<li>在经过页面更新以后，可以直接从主页面或者博客页面跳转，这时候就没有聊天用户 id 了</li>
<li>所以传获取消息时，receiverId 参数总为 undefined</li>
</ul>
</li>
<li>解决办法：<ul>
<li>对于私聊跳转，页面刷新，<strong>不能依赖于获取路由参数</strong>，而应该<strong>事先将变量保存</strong>，直接取到变量值即可</li>
<li>而对于大厅跳转，则应该根据选中的 tab 标签页，获取对应 key 值</li>
<li>应该做好<strong>tab 标签页 key 值</strong>的<strong>初始化以及动态更新</strong>的工作</li>
</ul>
</li>
</ul>
</li>
<li><p>根本解决不了，就应该在聊天大厅新增一个页面或窗口，<strong>大厅跳转进来是直接选中这个窗口的</strong>，</p>
</li>
<li><p>然后通过选中 tab 标签页，给 activeKey 赋值，这时候刷新页面就能停留在对应标签页上了</p>
</li>
<li><p>好极了，新增文件传输助手，页面跳转直接选中该 tab 标签页：</p>
</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-tab-pane v-model:activeKey=&quot;activeKey&quot; :key=&quot;currentUserId&quot; tab=&quot;文件传输助手&quot;<br>                      @click=&quot;handleTabChange&quot;&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// tab页 key</span><br><span class="hljs-keyword">const</span> activeKey = <span class="hljs-title function_">ref</span>(currentUserId);<br></code></pre></td></tr></table></figure>

<ul>
<li>何时跳转文件传输助手页？<ul>
<li>其他大厅首次跳转，进入聊天大厅页面，即 chatTabName.value.chatTabName === undefined</li>
<li>刷新页面，仅当当前 activeId 为 currentId 时</li>
</ul>
</li>
</ul>
<h3 id="Day12"><a href="#Day12" class="headerlink" title="Day12"></a>Day12</h3><ul>
<li>彻底解决刷新页面后，连接丢失的问题</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 监听 activeKey 的变化，更新存储中的值</span><br><span class="hljs-title function_">watch</span>(currentUserId, <span class="hljs-function">(<span class="hljs-params">value</span>) =&gt;</span> &#123;<br>  <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">setItem</span>(<span class="hljs-string">&quot;currentId&quot;</span>, value);<br>&#125;);<br></code></pre></td></tr></table></figure>

<ul>
<li>如上，我将 currentId 存放进 localStorage 中，这样在页面刷新后，可以从 localStorage 中取到当前用户 id，重新发起连接</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 钩子函数</span><br><span class="hljs-title function_">onMounted</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>  currentUserId = <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">&#x27;currentId&#x27;</span>)<br><span class="hljs-comment">// 主动连接</span><br>  <span class="hljs-title function_">openSocket</span>(currentUserId);<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 连接服务器</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">openSocket</span>(<span class="hljs-params">Id</span>) &#123;<br> .......................<br><br>    <span class="hljs-comment">//指定要连接的服务器地址与端口</span><br>    <span class="hljs-keyword">const</span> socketUrl = <span class="hljs-string">`ws://localhost:8081/api/websocket/<span class="hljs-subst">$&#123;Id&#125;</span>`</span>;<br>    <span class="hljs-keyword">if</span> (socket != <span class="hljs-literal">null</span>) &#123;<br>      socket.<span class="hljs-title function_">close</span>();<br>      socket = <span class="hljs-literal">null</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// 实例化WebSocket对象，建立连接</span><br>    socket = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocket</span>(socketUrl);<br><br> ................................<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>页面刷新之后，选中刚刚选中的 tab 页：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 监听 activeKey 的变化，更新存储中的值</span><br><span class="hljs-title function_">watch</span>(activeKey, <span class="hljs-function">(<span class="hljs-params">value</span>) =&gt;</span> &#123;<br>  <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">setItem</span>(<span class="hljs-string">&quot;activeKey&quot;</span>, value);<br>&#125;);<br><br>activeKey.<span class="hljs-property">value</span> = <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">&quot;activeKey&quot;</span>);<br></code></pre></td></tr></table></figure>

<ul>
<li>回收前面的解决连接丢失的问题，因为直接这行代码就没问题：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 主动连接</span><br><span class="hljs-title function_">openSocket</span>(currentUserId);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="真奇了怪了，一直都是这样写的，一直有问题，现在倒好，突然没问题了，连接不丢失了"><a href="#真奇了怪了，一直都是这样写的，一直有问题，现在倒好，突然没问题了，连接不丢失了" class="headerlink" title="真奇了怪了，一直都是这样写的，一直有问题，现在倒好，突然没问题了，连接不丢失了"></a>真奇了怪了，一直都是这样写的，一直有问题，现在倒好，突然没问题了，连接不丢失了</h5></li>
<li><p>难道是因为我之前多查询了下当前登录用户吗？</p>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-title function_">getCurrentUser</span>();<br><span class="hljs-comment">// 主动连接</span><br><span class="hljs-title function_">openSocket</span>(currentUserId);<br></code></pre></td></tr></table></figure>

<h3 id="Day13"><a href="#Day13" class="headerlink" title="Day13"></a>Day13</h3><ul>
<li><p>未登录时，进入聊天大厅获取当前登录用户的 id 时，会报异常</p>
<p><img src="http://blog.memory-life.xyz/image-20230921231210339.png" alt="image-20230921231210339"></p>
<ul>
<li><p>所以在跳转聊天大厅时，先校验是否登录</p>
</li>
<li><h5 id="如果未登录，则提醒用户先登录，才能享受实时通信服务"><a href="#如果未登录，则提醒用户先登录，才能享受实时通信服务" class="headerlink" title="如果未登录，则提醒用户先登录，才能享受实时通信服务"></a>如果未登录，则提醒用户先登录，才能享受实时通信服务</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 前往博客社区</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">goToChat</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (!currentUser.<span class="hljs-property">value</span>) &#123;<br>    message.<span class="hljs-title function_">warning</span>(<span class="hljs-string">&quot;请先登录&quot;</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    router.<span class="hljs-title function_">push</span>(<span class="hljs-string">&quot;/chat&quot;</span>);<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure></li>
<li><h5 id="优化用户体验："><a href="#优化用户体验：" class="headerlink" title="优化用户体验："></a>优化用户体验：</h5><ul>
<li>未登录时，不能跳转至聊天大厅</li>
<li>未登录时，不能查看个人信息</li>
</ul>
</li>
<li><h5 id="简单地优化了页面的表现张力：（2023-09-21-晚）"><a href="#简单地优化了页面的表现张力：（2023-09-21-晚）" class="headerlink" title="简单地优化了页面的表现张力：（2023/09/21 晚）"></a>简单地优化了页面的表现张力：（2023/09/21 晚）</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230922003153503.png" alt="image-20230922003153503"></p>
<p><img src="http://blog.memory-life.xyz/image-20230922003202984.png" alt="image-20230922003202984"></p>
<p><img src="http://blog.memory-life.xyz/image-20230922003211385.png" alt="image-20230922003211385"></p>
<h3 id="Day14"><a href="#Day14" class="headerlink" title="Day14"></a>Day14</h3><ul>
<li><p>一篇博客应该有什么信息呢？</p>
</li>
<li><p>完成博客列表优化：</p>
</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230923165701937.png" alt="image-20230923165701937"></p>
<ul>
<li><h5 id="我-HTML-CSS-的功底还是很不错的，基本没遇到什么大问题"><a href="#我-HTML-CSS-的功底还是很不错的，基本没遇到什么大问题" class="headerlink" title="我 HTML + CSS 的功底还是很不错的，基本没遇到什么大问题"></a>我 HTML + CSS 的功底还是很不错的，基本没遇到什么大问题</h5></li>
</ul>
<h3 id="现阶段优化指南"><a href="#现阶段优化指南" class="headerlink" title="现阶段优化指南"></a>现阶段优化指南</h3><ul>
<li><p>博客页面优化：</p>
<ul>
<li>如何实现每个博文绑定自己的 Like、Collect、Comment，待解决</li>
<li>博文应该带标签，点击对应标签，就可跳转至搜索的博文内容搜索结果</li>
<li>那就是要开发一个博文查找功能，开发博文查找页面，根据输入字段，查询匹配 title、description 的博文</li>
<li>待隔壁 MemorySearch 聚合搜索平台成功落地之后，会在 MemoryChat 博客社区中引入 ES，进行快速查找</li>
<li>开发博客编写页面，用户能够在线编写提交博文</li>
<li>用户收藏夹：多个收藏集，集合中博文的排列列表与搜索博文的结果展示，设想一致（掘金就这么干的）</li>
<li>评论功能，这个应该是核心，不过我肯定一样能轻松搞定</li>
</ul>
</li>
<li><p>队伍功能优化：</p>
<ul>
<li><h5 id="队长可以发布任务（或者说公告），在消息栏中显示"><a href="#队长可以发布任务（或者说公告），在消息栏中显示" class="headerlink" title="队长可以发布任务（或者说公告），在消息栏中显示"></a>队长可以发布任务（或者说公告），在消息栏中显示</h5></li>
<li><h5 id="待优化完成用户私聊的所有页面优化、通信体验之后，考虑开发队内聊天"><a href="#待优化完成用户私聊的所有页面优化、通信体验之后，考虑开发队内聊天" class="headerlink" title="待优化完成用户私聊的所有页面优化、通信体验之后，考虑开发队内聊天"></a>待优化完成用户私聊的所有页面优化、通信体验之后，考虑开发队内聊天</h5></li>
</ul>
</li>
<li><p>私聊体验优化：</p>
<ul>
<li>当有聊天消息还未接收时，有消息提醒（可供开启或关闭这个功能）</li>
<li><h5 id="私聊双方的消息，不够有辨识度"><a href="#私聊双方的消息，不够有辨识度" class="headerlink" title="私聊双方的消息，不够有辨识度"></a>私聊双方的消息，不够有辨识度</h5></li>
</ul>
</li>
<li><p>用户中心大厅优化</p>
<ul>
<li>将来计划把博客社区搞为默认首页，那用户中心是用来干什么的呢？（稍等一下，我问问 AI）</li>
<li>可以展示什么内容呢？介绍下这个网站的内容</li>
</ul>
</li>
</ul>
<h3 id="Day15"><a href="#Day15" class="headerlink" title="Day15"></a>Day15</h3><ul>
<li><p>搜索队伍、搜索用户、搜索博客将来统一处理</p>
</li>
<li><p>队伍状态（是否加密，显示） ✔</p>
</li>
<li><p>加密队伍，设置密码 ✔</p>
</li>
<li><h5 id="队伍状态区分，加密队伍有弹窗，提示输入密码"><a href="#队伍状态区分，加密队伍有弹窗，提示输入密码" class="headerlink" title="队伍状态区分，加密队伍有弹窗，提示输入密码"></a>队伍状态区分，加密队伍有弹窗，提示输入密码</h5></li>
<li><h5 id="队长可以发布公告"><a href="#队长可以发布公告" class="headerlink" title="队长可以发布公告"></a>队长可以发布公告</h5></li>
<li><p>解决了，几天前遗留下来的问题：</p>
<ul>
<li>其他大厅进入聊天大厅，默认文件传输助手</li>
<li>点击私聊进入聊天大厅，默认选中私聊用户</li>
</ul>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 获取接收者id</span><br>chatTabName.<span class="hljs-property">value</span> = route.<span class="hljs-property">query</span>;<br><span class="hljs-comment">// 记忆选中的Tab标签页</span><br><span class="hljs-keyword">if</span> (chatTabName.<span class="hljs-property">value</span> === <span class="hljs-literal">undefined</span>) &#123;<br>  activeKey.<span class="hljs-property">value</span> = <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">&quot;activeKey&quot;</span>);<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>  activeKey.<span class="hljs-property">value</span> = chatTabName.<span class="hljs-property">value</span>.<span class="hljs-property">chatTabName</span>;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h4 id="实时双向通信优化："><a href="#实时双向通信优化：" class="headerlink" title="实时双向通信优化："></a>实时双向通信优化：</h4><ul>
<li><h5 id="解决首次进入页面时，聊天消息未渲染的成功问题（这个好像很顽固）"><a href="#解决首次进入页面时，聊天消息未渲染的成功问题（这个好像很顽固）" class="headerlink" title="解决首次进入页面时，聊天消息未渲染的成功问题（这个好像很顽固）"></a>解决首次进入页面时，聊天消息未渲染的成功问题（这个好像很顽固）</h5></li>
<li><p>聊天消息要有辨识度</p>
<ul>
<li><p>简单地优化了输入框的样式，和表现张力</p>
</li>
<li><p>简单优化了消息的表现形式，目前发现的问题是：消息中没有发送者头像</p>
<ul>
<li><p>我们应该在在消息中插入头像字段嘛？这样在表现上会非常方便，但是扩展性太差，还得改动所有已有逻辑</p>
</li>
<li><p>那我们就应该根据 sendId 来查询发送者的信息，再渲染到消息上</p>
</li>
<li><h5 id="只要获取到了发送者的详细信息，就可以更清楚地排列消息，让收发双方的消息更加具有辨识度"><a href="#只要获取到了发送者的详细信息，就可以更清楚地排列消息，让收发双方的消息更加具有辨识度" class="headerlink" title="只要获取到了发送者的详细信息，就可以更清楚地排列消息，让收发双方的消息更加具有辨识度"></a>只要获取到了发送者的详细信息，就可以更清楚地排列消息，让收发双方的消息更加具有辨识度</h5></li>
</ul>
</li>
</ul>
</li>
<li><p>不能发送空消息</p>
<ul>
<li>简单地作了字符串校验，给予了发送成功和发送失败的反馈，在消息发送成功后，清空编辑的消息</li>
</ul>
</li>
</ul>
</li>
<li><h5 id="优化过后的通信体验（2023-09-24-午）"><a href="#优化过后的通信体验（2023-09-24-午）" class="headerlink" title="优化过后的通信体验（2023/09/24 午）"></a>优化过后的通信体验（2023/09/24 午）</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230924131722543.png" alt="image-20230924131722543"></p>
<ul>
<li><h4 id="博客页面优化计划"><a href="#博客页面优化计划" class="headerlink" title="博客页面优化计划"></a>博客页面优化计划</h4><ul>
<li>需要封装一个 DTO 类，返回文章，以及文章作者的所有信息</li>
<li>博客阅读页、博客收藏页、博客搜索页、博客编辑页</li>
</ul>
</li>
<li><p>博客阅读页的排版：</p>
<ul>
<li>文章内容、作者信息、文章目录、相关推荐</li>
</ul>
</li>
<li><p>完成点击不同的博文首页，跳转至不同的展示页面</p>
</li>
<li><p>博文阅读页的优化展示</p>
</li>
<li><h5 id="优化了博客阅读页，能够识别-Markdown-文档："><a href="#优化了博客阅读页，能够识别-Markdown-文档：" class="headerlink" title="优化了博客阅读页，能够识别 Markdown 文档："></a>优化了博客阅读页，能够识别 Markdown 文档：</h5></li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230924234514376.png" alt="image-20230924234514376"></p>
<figure class="highlight handlebars"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs handlebars"><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><br><span class="language-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">a-list-item-meta</span></span></span><br><span class="hljs-tag"><span class="language-xml">      <span class="hljs-attr">:description</span>=<span class="hljs-string">&quot;articleInfo.author.email&quot;</span></span></span><br><span class="hljs-tag"><span class="language-xml">  &gt;</span></span><br><span class="language-xml">    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">title</span>&gt;</span></span><br><span class="language-xml">      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;https://www.antdv.com/&quot;</span>&gt;</span></span><span class="hljs-template-variable">&#123;&#123;<span class="hljs-name">articleInfo.author.username</span>&#125;&#125;</span><span class="language-xml"> <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span><br><span class="language-xml">    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><span class="language-xml">  <span class="hljs-tag">&lt;/<span class="hljs-name">a-list-item-meta</span>&gt;</span></span><br><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="今日待解决："><a href="#今日待解决：" class="headerlink" title="今日待解决："></a>今日待解决：</h5><ul>
<li>私密队伍需输入密码才可申请加入 ✔😈</li>
<li>实时消息没有获取到发送人的详细信息 😈</li>
<li>发布队内公告 ✔😈</li>
<li>首次进入页面时，聊天消息未渲染的成功 ✔😈</li>
<li>博文列表太长了，应该加个每页数量和延时加载效果</li>
<li>识别 Markdown 格式 ✔</li>
<li>博文收藏页、博文搜索页</li>
</ul>
</li>
</ul>
<h3 id="Day16"><a href="#Day16" class="headerlink" title="Day16"></a>Day16</h3><ul>
<li><p>先解决昨天的问题</p>
</li>
<li><h5 id="用户新增文章数、获赞数、粉丝数"><a href="#用户新增文章数、获赞数、粉丝数" class="headerlink" title="用户新增文章数、获赞数、粉丝数"></a>用户新增文章数、获赞数、粉丝数</h5></li>
<li><h5 id="尝试使用-CSS-实现页面跳转动画，但-Vue-渲染冲突，导致动画重复渲染"><a href="#尝试使用-CSS-实现页面跳转动画，但-Vue-渲染冲突，导致动画重复渲染" class="headerlink" title="尝试使用 CSS 实现页面跳转动画，但 Vue 渲染冲突，导致动画重复渲染"></a>尝试使用 CSS 实现页面跳转动画，但 Vue 渲染冲突，导致动画重复渲染</h5></li>
</ul>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs css"><span class="hljs-selector-class">.fade-in-out</span> &#123;<br>  <span class="hljs-attribute">animation</span>: fadeInOut <span class="hljs-number">5s</span>;<br>&#125;<br><br><span class="hljs-keyword">@keyframes</span> fadeInOut &#123;<br>  <span class="hljs-number">0%</span> &#123;<br>    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;<br>  &#125;<br>  <span class="hljs-number">50%</span> &#123;<br>    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;<br>  &#125;<br>  <span class="hljs-number">100%</span> &#123;<br>    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="使用-a-affix-组件将-元素固定，这个时候元素的-width-属性不能是百分比了，否则不起作用"><a href="#使用-a-affix-组件将-元素固定，这个时候元素的-width-属性不能是百分比了，否则不起作用" class="headerlink" title="使用 a-affix 组件将 元素固定，这个时候元素的 width 属性不能是百分比了，否则不起作用"></a>使用 a-affix 组件将 元素固定，这个时候元素的 width 属性不能是百分比了，否则不起作用</h5></li>
<li><p>固定每周推荐用户、作者、点赞信息</p>
</li>
<li><h5 id="组件撑不开盒子，老有这个问题"><a href="#组件撑不开盒子，老有这个问题" class="headerlink" title="组件撑不开盒子，老有这个问题"></a>组件撑不开盒子，老有这个问题</h5><ul>
<li>妈的，组件存在浮动、定位属性，当然撑不开了</li>
</ul>
</li>
<li><h5 id="成功解决点赞、收藏功能（每篇文章独有）"><a href="#成功解决点赞、收藏功能（每篇文章独有）" class="headerlink" title="成功解决点赞、收藏功能（每篇文章独有）"></a>成功解决点赞、收藏功能（每篇文章独有）</h5></li>
<li><p>解决这个问题的关键就是，为每个文章都分配独有的判定点赞、判定收藏变量：</p>
</li>
<li><p>在获取博文列表时，为每个博文增加这几个字段：</p>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs ts">myAxios<br>  .<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;/article/list/VO&quot;</span>, &#123;<br>    <span class="hljs-attr">params</span>: &#123;<br>      <span class="hljs-attr">currentPage</span>: <span class="hljs-number">1</span>,<br>      <span class="hljs-attr">pageSize</span>: <span class="hljs-number">5</span>,<br>    &#125;,<br>  &#125;)<br>  .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>    <span class="hljs-keyword">if</span> (res.<span class="hljs-property">data</span> !== <span class="hljs-literal">null</span>) &#123;<br>      articleList.<span class="hljs-property">value</span> = res.<span class="hljs-property">data</span>.<span class="hljs-property">records</span>.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">article: <span class="hljs-built_in">any</span></span>) =&gt;</span> &#123;<br>        <span class="hljs-keyword">return</span> &#123;<br>          ...article,<br>          <span class="hljs-attr">isLiked</span>: <span class="hljs-literal">false</span>,<br>          <span class="hljs-attr">isCollected</span>: <span class="hljs-literal">false</span>,<br>          <span class="hljs-attr">isComment</span>: <span class="hljs-literal">false</span>,<br>        &#125;;<br>      &#125;);<br>    &#125;<br>  &#125;);<br></code></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;LikeTwoTone<br>  style=&quot;font-size: 18px&quot;<br>  key=&quot;like&quot;<br>  :twoToneColor=&quot;item.isLiked ? &#x27;#ff4d4f&#x27; : &#x27;#b0c4d8&#x27;&quot;<br>  @click=&quot;toggleLike(item)&quot;<br>/&gt;<br><br>&lt;HeartTwoTone<br>  style=&quot;font-size: 18px&quot;<br>  key=&quot;collect&quot;<br>  :twoToneColor=&quot;item.isCollected ? &#x27;#ff4d4f&#x27; : &#x27;#b0c4d8&#x27;&quot;<br>  @click=&quot;toggleCollect(item)&quot;<br>/&gt;<br>&lt;MessageTwoTone<br>  style=&quot;font-size: 18px&quot;<br>  key=&quot;comment&quot;<br>  :twoToneColor=&quot;item.isComment ? &#x27;#ff4d4f&#x27; : &#x27;#b0c4d8&#x27;&quot;<br>/&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 点赞</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">toggleLike</span> = (<span class="hljs-params">item</span>) =&gt; &#123;<br>  item.<span class="hljs-property">isLiked</span> = !item.<span class="hljs-property">isLiked</span>;<br>&#125;;<br><span class="hljs-comment">// 收藏</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">toggleCollect</span> = (<span class="hljs-params">item</span>) =&gt; &#123;<br>  item.<span class="hljs-property">isCollected</span> = !item.<span class="hljs-property">isCollected</span>;<br>&#125;;<br></code></pre></td></tr></table></figure>

<p><img src="http://blog.memory-life.xyz/image-20230925151826913.png" alt="image-20230925151826913"></p>
<h3 id="Day17"><a href="#Day17" class="headerlink" title="Day17"></a>Day17</h3><ul>
<li><p>封装消息的内容</p>
<ul>
<li><p>可以在后台封装</p>
</li>
<li><h5 id="也可以选择前端发出请求（这个简便）"><a href="#也可以选择前端发出请求（这个简便）" class="headerlink" title="也可以选择前端发出请求（这个简便）"></a>也可以选择前端发出请求（这个简便）</h5></li>
</ul>
</li>
<li><p>搞没了，前端拿取每条消息的每个 id 再发送请求，简直做梦，还是搞后端吧</p>
</li>
<li><p>现在把头像换成登录用户的头像</p>
</li>
<li><p>简单地优化了双向通信的消息气泡展示：</p>
<ul>
<li><h5 id="标签中定义了动态的-class-属性：（2023-09-26-午）"><a href="#标签中定义了动态的-class-属性：（2023-09-26-午）" class="headerlink" title="标签中定义了动态的 class 属性：（2023/09/26 午）"></a>标签中定义了动态的 class 属性：（2023/09/26 午）</h5></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;div :class=&quot;messageClass(item.senderId)&quot;&gt;<br>          &lt;!--发送者--&gt;<br>       &lt;div style=&quot;position: absolute;top: 6px&quot;&gt;<br>             &lt;a-avatar size=&quot;large&quot; :src=&quot;currentUser.avatarUrl&quot;/&gt;<br>                 &lt;/div&gt;<br>                  &lt;div style=&quot;margin-left: 24px;font-size: medium&quot;&gt;<br>                    &lt;a-list-item&gt;<br>                       &lt;div style=&quot;font-size: smaller&quot;&gt;<br>                      &lt;!--发送时间--&gt;<br>                      &lt;a-list-item-meta :description=&quot;item.sendTime&quot;&gt;<br>                      &lt;/a-list-item-meta&gt;<br>                     &lt;/div&gt;<br>                 &lt;!--消息内容--&gt;<br>             &#123;&#123; item.content &#125;&#125;<br>                   &lt;/a-list-item&gt;<br>              &lt;/div&gt;<br>       &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs css"><span class="hljs-selector-class">.senderMsg</span> &#123;<br>  <span class="hljs-attribute">position</span>: relative;<br>  <span class="hljs-attribute">left</span>: <span class="hljs-number">1150px</span>;<br>&#125;<br><br><span class="hljs-selector-class">.receiverMsg</span> &#123;<br>  <span class="hljs-attribute">position</span>: relative;<br>&#125;<br><br><span class="hljs-selector-class">.receiverMsg</span> &#123;<br>  <span class="hljs-attribute">position</span>: relative;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="动态改变-class"><a href="#动态改变-class" class="headerlink" title="动态改变 class"></a>动态改变 class</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 动态变换收发双方消息气泡</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">messageClass</span> = (<span class="hljs-params">senderId</span>) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (currentUserId === senderId) &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;senderMsg&quot;</span>; <span class="hljs-comment">// 如果当前用户的ID等于消息发送者的ID，</span><br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;receiverMsg&quot;</span>; <span class="hljs-comment">// 其他情况返回空字符串</span><br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure></li>
<li><p>最终效果：</p>
</li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230926170307814.png" alt="image-20230926170307814"></p>
<ul>
<li><h5 id="关于通信双方的详细信息，还得在后端封装，暂时不改了"><a href="#关于通信双方的详细信息，还得在后端封装，暂时不改了" class="headerlink" title="关于通信双方的详细信息，还得在后端封装，暂时不改了"></a>关于通信双方的详细信息，还得在后端封装，暂时不改了</h5></li>
<li><h5 id="当文章高度不够全屏时，背景默认为整个屏幕高度；随着内容增多，背景高度会动态增加"><a href="#当文章高度不够全屏时，背景默认为整个屏幕高度；随着内容增多，背景高度会动态增加" class="headerlink" title="当文章高度不够全屏时，背景默认为整个屏幕高度；随着内容增多，背景高度会动态增加"></a>当文章高度不够全屏时，背景默认为整个屏幕高度；随着内容增多，背景高度会动态增加</h5></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;!--文章信息--&gt;<br>     &lt;a-card style=&quot;width: 60%;margin-left: 15%;  min-height: 85vh;margin-bottom: 20px&quot;&gt;<br></code></pre></td></tr></table></figure>

<p><img src="http://blog.memory-life.xyz/image-20230926220346416.png" alt="image-20230926220346416"></p>
<ul>
<li>简单实现了申请加密队伍需要填写密码的功能：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 申请加入队伍</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">joinTeam</span> = (<span class="hljs-params">teamInfo: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (teamInfo.<span class="hljs-property">status</span> === <span class="hljs-number">2</span>) &#123;<br>    <span class="hljs-title function_">showModal</span>();<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    myAxios<br>      .<span class="hljs-title function_">post</span>(<span class="hljs-string">&quot;/team/join&quot;</span>, teamInfo)<br>      .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>        message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;申请入队成功&quot;</span>);<br>      &#125;)<br>      .<span class="hljs-title function_">catch</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;加入失败&quot;</span>);<br>      &#125;);<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 确认</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleOk</span> = (<span class="hljs-params">teamInfo: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  vis.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span>;<br><br>  myAxios<br>    .<span class="hljs-title function_">post</span>(<span class="hljs-string">&quot;/team/join&quot;</span>, teamInfo)<br>    .<span class="hljs-title function_">then</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>      message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;申请入队成功&quot;</span>);<br>    &#125;)<br>    .<span class="hljs-title function_">catch</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;加入失败&quot;</span>);<br>    &#125;);<br>&#125;;<br></code></pre></td></tr></table></figure>

<p><img src="http://blog.memory-life.xyz/image-20230926235757172.png" alt="image-20230926235757172"></p>
<ul>
<li><h5 id="完成队伍公告的展示，再实现一个点击查看详细公告吧（2023-09-26-晚）"><a href="#完成队伍公告的展示，再实现一个点击查看详细公告吧（2023-09-26-晚）" class="headerlink" title="完成队伍公告的展示，再实现一个点击查看详细公告吧（2023/09/26 晚）"></a>完成队伍公告的展示，再实现一个点击查看详细公告吧（2023/09/26 晚）</h5></li>
<li><p>查看队内公告完成</p>
</li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230926235739393.png" alt="image-20230926235739393"></p>
<ul>
<li>申请添加好友/进入队伍之前，都应该有弹窗进行反复确认</li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230927000836068.png" alt="image-20230927000836068"></p>
<h3 id="Day18"><a href="#Day18" class="headerlink" title="Day18"></a>Day18</h3><ul>
<li><h5 id="简单的评论展示："><a href="#简单的评论展示：" class="headerlink" title="简单的评论展示："></a>简单的评论展示：</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230927112918187.png" alt="image-20230927112918187"></p>
<ul>
<li>简单的博文收藏页：</li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230927115337147.png" alt="image-20230927115337147"></p>
<ul>
<li><p>后端，博文表的字段添加</p>
<ul>
<li>收藏量（collects）</li>
<li>封面图（articleUrl）</li>
<li>标签（tags）</li>
</ul>
</li>
<li><h5 id="点击评论图标也可以进入页面"><a href="#点击评论图标也可以进入页面" class="headerlink" title="点击评论图标也可以进入页面"></a>点击评论图标也可以进入页面</h5></li>
<li><h5 id="优化下队伍人数的表现、每周推荐用户的匹配度表现"><a href="#优化下队伍人数的表现、每周推荐用户的匹配度表现" class="headerlink" title="优化下队伍人数的表现、每周推荐用户的匹配度表现"></a>优化下队伍人数的表现、每周推荐用户的匹配度表现</h5></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-progress type=&quot;circle&quot; :percent=&quot;item.percentage&quot; :width=&quot;50&quot; /&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="这个圆形进度圈的大小，调整起来有点问题"><a href="#这个圆形进度圈的大小，调整起来有点问题" class="headerlink" title="这个圆形进度圈的大小，调整起来有点问题"></a>这个圆形进度圈的大小，调整起来有点问题</h5></li>
<li><p>嗨嗨，这个进度条的百分比还让我自己算：</p>
</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-progress<br>  :percent=&quot;((item.joinNum / item.maxNum) * 100).toFixed(2)&quot;<br>  size=&quot;small&quot;<br>  status=&quot;active&quot;<br>/&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>解决一下默认打开用户中心 -&gt; 我的页面时，没有渲染出已加入的队伍列表</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 默认选中已加入的队伍</span><br><span class="hljs-keyword">const</span> activeKey = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&quot;1&quot;</span>);<br><span class="hljs-comment">// 直接查询已加入的队伍</span><br><span class="hljs-title function_">onMounted</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>  <span class="hljs-title function_">getJoinedTeam</span>();<br>&#125;);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="给队伍表添加队内公告字段"><a href="#给队伍表添加队内公告字段" class="headerlink" title="给队伍表添加队内公告字段"></a>给队伍表添加队内公告字段</h5></li>
<li><h5 id="修改公告这个还需要考虑一下："><a href="#修改公告这个还需要考虑一下：" class="headerlink" title="修改公告这个还需要考虑一下："></a>修改公告这个还需要考虑一下：</h5><ul>
<li>传入的参数：修改人、修改队伍、公告内容（只有队长才能修改/发布公告）</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> Boolean <span class="hljs-title function_">updateAnnouncement</span><span class="hljs-params">(AddAnnouncement addAnnouncement, HttpServletRequest request)</span> &#123;<br>       <span class="hljs-type">Long</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> addAnnouncement.getUserId();<br>       <span class="hljs-type">Long</span> <span class="hljs-variable">teamId</span> <span class="hljs-operator">=</span> addAnnouncement.getTeamId();<br>       <span class="hljs-type">String</span> <span class="hljs-variable">announcement</span> <span class="hljs-operator">=</span> addAnnouncement.getAnnouncement();<br><br>       <span class="hljs-keyword">if</span> (!getById(teamId).getUserId().equals(userId))<br>           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.NO_AUTH, <span class="hljs-string">&quot;非队长不能更新公告内容&quot;</span>);<br><br>       UpdateWrapper&lt;Team&gt; tuw = <span class="hljs-keyword">new</span> <span class="hljs-title class_">UpdateWrapper</span>&lt;&gt;();<br>       tuw.eq(<span class="hljs-string">&quot;id&quot;</span>,teamId).set(<span class="hljs-string">&quot;announcement&quot;</span>, announcement);<br>       <span class="hljs-type">boolean</span> <span class="hljs-variable">update</span> <span class="hljs-operator">=</span> update(tuw);<br><br>       <span class="hljs-keyword">if</span> (!update)<br>           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.UPDATE_ERROR, <span class="hljs-string">&quot;更新公告内容失败&quot;</span>);<br><br>       <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>   &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="后台接口已开发完毕，前端接口待调通（2023-09-27-晚）"><a href="#后台接口已开发完毕，前端接口待调通（2023-09-27-晚）" class="headerlink" title="后台接口已开发完毕，前端接口待调通（2023/09/27 晚）"></a>后台接口已开发完毕，前端接口待调通（2023/09/27 晚）</h5></li>
</ul>
<h3 id="Day19"><a href="#Day19" class="headerlink" title="Day19"></a>Day19</h3><ul>
<li><p>完善昨晚的发布公告，遇到了些许小问题：</p>
<ul>
<li>由于编辑公告的弹窗是写在 list 列表中的，即每个 team 都有自己的公告弹窗，所以不应该使用一个变量来控制弹窗的出现/消失：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 弹窗</span><br><span class="hljs-keyword">const</span> visible = ref&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);<br><span class="hljs-comment">// 展示弹窗</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">showModal</span> = (<span class="hljs-params">team: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  visible.<span class="hljs-property">value</span> = <span class="hljs-literal">true</span>;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>而应该为每个 team，都分配独有的控制弹窗出现的变量（<strong>showEditWindow</strong>）：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs ts">myAxios<br>  .<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;/team/created&quot;</span>, &#123;<br>    <span class="hljs-attr">params</span>: &#123;<br>      <span class="hljs-attr">loginUserId</span>: userId,<br>    &#125;,<br>  &#125;)<br>  .<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>    createdTeamList.<span class="hljs-property">value</span> = res.<span class="hljs-property">data</span>.<span class="hljs-property">records</span>.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">team: <span class="hljs-built_in">any</span></span>) =&gt;</span> &#123;<br>      <span class="hljs-keyword">return</span> &#123;<br>        ...team,<br>        <span class="hljs-attr">showEditWindow</span>: <span class="hljs-literal">false</span>,<br>      &#125;;<br>    &#125;);<br>  &#125;);<br></code></pre></td></tr></table></figure>

<ul>
<li>为每一个 team 绑定：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-button size=&quot;large&quot; danger @click=&quot;showModal(item)&quot;&gt;发布公告&lt;/a-button&gt;<br>&lt;div style=&quot;position: relative&quot;&gt;<br>                    &lt;a-modal v-model:visible=&quot;item.showEditWindow&quot; title=&quot;队伍公告&quot; @ok=&quot;updateAnnouncement(item)&quot;&gt;<br>                      &lt;p&gt;请编辑队伍公告&lt;/p&gt;<br>                      &lt;a-textarea v-model:value=&quot;announcement&quot; style=&quot;height: 100px&quot;/&gt;<br>                    &lt;/a-modal&gt;<br>                  &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 公告弹窗</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">showModal</span> = (<span class="hljs-params">team: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  team.<span class="hljs-property">showEditWindow</span> = <span class="hljs-literal">true</span>;<br>&#125;;<br></code></pre></td></tr></table></figure></li>
<li><p>这个思路是普适的，在获取后台传回的数组信息时，为数组内每一个元素都封装自己的独有的元素</p>
</li>
<li><p>在<strong>实现每篇文章的点赞、收藏</strong>，以及<strong>申请加入队伍</strong>中，都是这样的思路<strong>（2023/09/28 午）</strong></p>
</li>
<li><p>实现博文列表 <strong>加载更多</strong> 功能</p>
<ul>
<li><h5 id="在评论加载中，也同样适用"><a href="#在评论加载中，也同样适用" class="headerlink" title="在评论加载中，也同样适用"></a>在评论加载中，也同样适用</h5></li>
<li><p>放弃了，搞不懂实现原理，日后再看</p>
</li>
</ul>
</li>
</ul>
<h4 id="新增文章评论功能"><a href="#新增文章评论功能" class="headerlink" title="新增文章评论功能"></a>新增文章评论功能</h4><ul>
<li><p>后端新建评论表：</p>
</li>
<li><p>这个评论表该如何构建呢？表字段：</p>
<ul>
<li>评论 id、子评论 id、发评者 id、评论内容、评论时间</li>
</ul>
</li>
<li><h5 id="文章如何绑定评论？"><a href="#文章如何绑定评论？" class="headerlink" title="文章如何绑定评论？"></a>文章如何绑定评论？</h5><ul>
<li>设计一张文章/评论表（太庞大了，每篇文章与其评论的关系都要一一列举）</li>
<li>文章添加字段：评论字段（comments），存储评论 id，当然，仅存取<strong>一级评论 id</strong></li>
<li>二级评论 id 当然要从一级评论的字段中找了，子评论字段（comments）存储二级评论 id</li>
</ul>
</li>
</ul>
<h3 id="Day20"><a href="#Day20" class="headerlink" title="Day20"></a>Day20</h3><ul>
<li><h5 id="彻底优化实时双向通信的页面、消息气泡表现方式"><a href="#彻底优化实时双向通信的页面、消息气泡表现方式" class="headerlink" title="彻底优化实时双向通信的页面、消息气泡表现方式"></a>彻底优化实时双向通信的页面、消息气泡表现方式</h5></li>
<li><h5 id="后端封装-MessageVO-类，在-Message-基础上扩展了两个字段（目前仅需要这两个字段，后续可以做扩展）："><a href="#后端封装-MessageVO-类，在-Message-基础上扩展了两个字段（目前仅需要这两个字段，后续可以做扩展）：" class="headerlink" title="后端封装 MessageVO 类，在 Message 基础上扩展了两个字段（目前仅需要这两个字段，后续可以做扩展）："></a>后端封装 MessageVO 类，在 Message 基础上扩展了两个字段（目前仅需要这两个字段，后续可以做扩展）：</h5></li>
</ul>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 发送者昵称</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">String</span> username;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 发送者头像</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">String</span> avatarUrl;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="提供了转换-messageList-为-MessageVOList-的方法"><a href="#提供了转换-messageList-为-MessageVOList-的方法" class="headerlink" title="提供了转换 messageList 为 MessageVOList 的方法"></a>提供了转换 messageList 为 MessageVOList 的方法</h5></li>
</ul>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 转换 messageList 为 MessageVOList</span><br><span class="hljs-comment">    *</span><br><span class="hljs-comment">    * @param messageList messageList</span><br><span class="hljs-comment">    * @return MessageVO</span><br><span class="hljs-comment">    */</span><br>   public List&lt;MessageVO&gt; get<span class="hljs-constructor">MessageVOByMessage(List&lt;Message&gt; <span class="hljs-params">messageList</span>)</span> &#123;<br>       return messageList.stream<span class="hljs-literal">()</span>.map(message -&gt; &#123;<br>           Long senderId = message.get<span class="hljs-constructor">SenderId()</span>;<br>           User user = userService.get<span class="hljs-constructor">ById(<span class="hljs-params">senderId</span>)</span>;<br><br>           MessageVO messageVO = <span class="hljs-keyword">new</span> <span class="hljs-constructor">MessageVO()</span>;<br>           messageVO.set<span class="hljs-constructor">SenderId(<span class="hljs-params">message</span>.<span class="hljs-params">getSenderId</span>()</span>);<br>           messageVO.set<span class="hljs-constructor">ReceiverId(<span class="hljs-params">message</span>.<span class="hljs-params">getReceiverId</span>()</span>);<br>           messageVO.set<span class="hljs-constructor">Content(<span class="hljs-params">message</span>.<span class="hljs-params">getContent</span>()</span>);<br>           messageVO.set<span class="hljs-constructor">SendTime(<span class="hljs-params">message</span>.<span class="hljs-params">getSendTime</span>()</span>);<br>           messageVO.set<span class="hljs-constructor">Username(<span class="hljs-params">user</span>.<span class="hljs-params">getUsername</span>()</span>);<br>           messageVO.set<span class="hljs-constructor">AvatarUrl(<span class="hljs-params">user</span>.<span class="hljs-params">getAvatarUrl</span>()</span>);<br><br>           return messageVO;<br>       &#125;).collect(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Collectors</span>.</span></span><span class="hljs-keyword">to</span><span class="hljs-constructor">List()</span>);<br>   &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="完全优化了实时双向通信的用户体验："><a href="#完全优化了实时双向通信的用户体验：" class="headerlink" title="完全优化了实时双向通信的用户体验："></a>完全优化了实时双向通信的用户体验：</h5></li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230930220121786.png" alt="image-20230930220121786"></p>
<ul>
<li><p>申请入队：</p>
<ul>
<li><h5 id="想实现推动入队消息到队长的消息列表中，队长同意，方可入队"><a href="#想实现推动入队消息到队长的消息列表中，队长同意，方可入队" class="headerlink" title="想实现推动入队消息到队长的消息列表中，队长同意，方可入队"></a>想实现推动入队消息到队长的消息列表中，队长同意，方可入队</h5></li>
<li><h5 id="学完消息队列之后再考虑"><a href="#学完消息队列之后再考虑" class="headerlink" title="学完消息队列之后再考虑"></a>学完消息队列之后再考虑</h5></li>
</ul>
</li>
<li><p>点赞、收藏博文，</p>
</li>
<li><h5 id="后端接口开发："><a href="#后端接口开发：" class="headerlink" title="后端接口开发："></a>后端接口开发：</h5><ul>
<li><p>点赞/收藏博文，应该传入的参数有：</p>
</li>
<li><p>userId、ArticleId</p>
</li>
<li><p>执行使该博文的 likes + 1，并在 article-like 表中存放点赞记录</p>
</li>
<li><p>收藏博文也是如此</p>
</li>
<li><h5 id="慢慢实现吧，最近先把简历优化、算法拔高、项目优化、软考备考做好（2023-09-30-晚）"><a href="#慢慢实现吧，最近先把简历优化、算法拔高、项目优化、软考备考做好（2023-09-30-晚）" class="headerlink" title="慢慢实现吧，最近先把简历优化、算法拔高、项目优化、软考备考做好（2023/09/30 晚）"></a>慢慢实现吧，最近先把简历优化、算法拔高、项目优化、软考备考做好（2023/09/30 晚）</h5></li>
</ul>
</li>
</ul>
<h4 id="MemoryChat-开发计划："><a href="#MemoryChat-开发计划：" class="headerlink" title="MemoryChat 开发计划："></a>MemoryChat 开发计划：</h4><ul>
<li><h5 id="使用-Redis-缓存热点消息（最近-36h-内的消息），数据库中存放所有消息"><a href="#使用-Redis-缓存热点消息（最近-36h-内的消息），数据库中存放所有消息" class="headerlink" title="使用 Redis 缓存热点消息（最近 36h 内的消息），数据库中存放所有消息"></a>使用 Redis 缓存热点消息（最近 36h 内的消息），数据库中存放所有消息</h5></li>
<li><h5 id="使用-ES-来大幅提升博文检索效率"><a href="#使用-ES-来大幅提升博文检索效率" class="headerlink" title="使用 ES 来大幅提升博文检索效率"></a>使用 ES 来大幅提升博文检索效率</h5></li>
</ul>
<h2 id="经验分享"><a href="#经验分享" class="headerlink" title="经验分享"></a>经验分享</h2><ul>
<li>如何实现点击好友私聊后，跳转至聊天窗口的对应聊天窗口？</li>
<li>实现流程：<ul>
<li>进入聊天大厅，直接查询<strong>我的所有好友信息</strong></li>
<li>聊天大厅直接以<strong>tab 标签页</strong>的形式，展示了<strong>所有好友的聊天窗口</strong>，其中，各个<strong>tab 页的 key 值为好友的 id</strong></li>
<li>点击私聊，<strong>携带好友 id</strong>作为参数，从我的好友列表跳转至聊天大厅</li>
<li>跳转至聊天大厅，直接<strong>获取传递的 id 参数</strong>，并<strong>将其值赋给 tab 页的 activeKey</strong></li>
<li>tabs 标签页默认<strong>选中 key 值为 activeKey</strong> 的 tab 页</li>
</ul>
</li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230916141753064.png" alt="image-20230916141753064"></p>
<p><img src="http://blog.memory-life.xyz/image-20230916141758222.png" alt="image-20230916141758222"></p>
<ul>
<li>接下来就是在各个聊天窗口，实现双向实时通信了</li>
</ul>
<h3 id="网页中支持-Markdown-语法写博客"><a href="#网页中支持-Markdown-语法写博客" class="headerlink" title="网页中支持 Markdown 语法写博客"></a>网页中支持 Markdown 语法写博客</h3><ul>
<li><h5 id="要在博客网站中支持-Markdown-语法写博客并展示，你可以使用第三方的-Markdown-解析库来解析-Markdown-文本，并将解析后的内容展示在网页上（2023-09-24-晚）"><a href="#要在博客网站中支持-Markdown-语法写博客并展示，你可以使用第三方的-Markdown-解析库来解析-Markdown-文本，并将解析后的内容展示在网页上（2023-09-24-晚）" class="headerlink" title="要在博客网站中支持 Markdown 语法写博客并展示，你可以使用第三方的 Markdown 解析库来解析 Markdown 文本，并将解析后的内容展示在网页上（2023/09/24 晚）"></a>要在博客网站中支持 Markdown 语法写博客并展示，你可以使用第三方的 Markdown 解析库来解析 Markdown 文本，并将解析后的内容展示在网页上（2023/09/24 晚）</h5></li>
<li><p>首先，你需要引入一个适用于 Vue 的 Markdown 解析库，例如<code>markdown-it</code>。可以通过 NPM 安装该库：</p>
</li>
</ul>
<figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs livescript"><span class="hljs-built_in">npm</span> install markdown-<span class="hljs-literal">it</span><br></code></pre></td></tr></table></figure>

<ul>
<li>然后，在你的组件中，你可以导入并实例化<code>markdown-it</code>，将 Markdown 文本作为输入，使用<code>.render()</code>方法将其转换为 HTML 并展示在网页上</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">import</span> <span class="hljs-title class_">MarkdownIt</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;markdown-it&quot;</span>;<br><br><span class="hljs-comment">// Markdown语法</span><br><span class="hljs-keyword">const</span> parsedContent = <span class="hljs-title function_">ref</span>();<br><span class="hljs-keyword">const</span> md = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MarkdownIt</span>();<br><span class="hljs-comment">// 使用Markdown语法接收文章内容</span><br>parsedContent.<span class="hljs-property">value</span> = md.<span class="hljs-title function_">render</span>(articleInfo.<span class="hljs-property">value</span>.<span class="hljs-property">content</span>);<br></code></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;div<br>  v-html=&quot;parsedContent&quot;<br>  style=&quot;position: absolute; margin-left: 10px; margin-right: 10px; margin-top: 20px;&quot;<br>&gt;<br> &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>在上面的示例代码中，我们导入了<code>markdown-it</code>库</p>
<ul>
<li>然后在<code>mounted</code>钩子中实例化了<code>MarkdownIt</code>对象</li>
<li>并将 Markdown 文本<code>this.articleInfo.content</code>传递给其<code>.render()</code>方法来解析为 HTML 并赋值给<code>parsedContent</code></li>
<li>然后我们使用<code>v-html</code>指令将解析后的内容展示在网页上。</li>
</ul>
</li>
<li><p>这样，无论用户使用 Markdown 语法还是普通的 HTML 编写博客内容，页面都会正确展示</p>
</li>
<li><p>最终效果：</p>
</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230924235124466.png" alt="image-20230924235124466"></p>
<h2 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h2><h3 id="JSON-转换错误"><a href="#JSON-转换错误" class="headerlink" title="JSON 转换错误"></a>JSON 转换错误</h3><ul>
<li><h5 id="前台获取服务器转发的消息后，需要解析出接收者，再判断接收者是否为当前用户，是则为该用户展示具体消息"><a href="#前台获取服务器转发的消息后，需要解析出接收者，再判断接收者是否为当前用户，是则为该用户展示具体消息" class="headerlink" title="前台获取服务器转发的消息后，需要解析出接收者，再判断接收者是否为当前用户，是则为该用户展示具体消息"></a>前台获取服务器转发的消息后，需要解析出接收者，再判断接收者是否为当前用户，是则为该用户展示具体消息</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">//获得消息事件(获得服务端转发的消息)</span><br>socket.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">msg</span>) &#123;<br>  <span class="hljs-comment">// 封装返回消息</span><br>  receiveMsg.<span class="hljs-property">value</span> = msg.<span class="hljs-property">data</span>;<br>  <span class="hljs-comment">// 解构</span><br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;这条消息是发给: &quot;</span> + receiveMsg.<span class="hljs-property">value</span> + <span class="hljs-string">&quot; 的&quot;</span>);<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;这条消息是发给: &quot;</span> + receiveMsg.<span class="hljs-property">value</span>.<span class="hljs-property">receiverId</span> + <span class="hljs-string">&quot; 的&quot;</span>);<br>  <span class="hljs-keyword">const</span> content = receiveMsg.<span class="hljs-property">value</span>.<span class="hljs-property">content</span>;<br>  <span class="hljs-comment">// 是否属于我的消息</span><br>  <span class="hljs-keyword">if</span> (receiverId === currentUserId) &#123;<br>    <span class="hljs-title function_">setMessage</span>(<span class="hljs-string">&quot;服务端回应: &quot;</span> + content + <span class="hljs-string">&quot;发给: &quot;</span> + receiverId);<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>前端返回的 mes 是 JSON 字符串，我看见控制台输出的内容是，以为<strong>是个对象</strong>，结果取到的属性值是 undefined：</li>
</ul>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;senderId&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;1657284783190523906&quot;</span><span class="hljs-punctuation">,</span><span class="hljs-attr">&quot;receiverId&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;1657284893320364034&quot;</span><span class="hljs-punctuation">,</span><span class="hljs-attr">&quot;content&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;阿发&quot;</span><span class="hljs-punctuation">,</span><span class="hljs-attr">&quot;sendTime&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;2023-09-14T13:29:11.309Z&quot;</span><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure>

<ul>
<li>调试了半天，检查了下 mes 的类型，才发现是个<strong>JSON 字符串</strong>，奶奶的</li>
</ul>
<figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs arcade"><span class="hljs-built_in">console</span>.<span class="hljs-built_in">log</span>(<span class="hljs-string">&quot;type of &quot;</span> + <span class="hljs-built_in">typeof</span> (receiveMsg.value))<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="所以要将-msg-JSON-字符串解析为对象后，才可以正常拿到属性值"><a href="#所以要将-msg-JSON-字符串解析为对象后，才可以正常拿到属性值" class="headerlink" title="所以要将 msg JSON 字符串解析为对象后，才可以正常拿到属性值"></a>所以要将 msg JSON 字符串解析为对象后，才可以正常拿到属性值</h5></li>
</ul>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">receiveMsg.value</span> = JSON.parse(msg.data)<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><p>但是连接服务器 socket 时，也会执行这里的代码，而<strong>服务器首次响应连接请求并传回的的参数类型</strong>却<strong>不是 JSON 字符串</strong></p>
</li>
<li><h5 id="解析失败，报以下错误："><a href="#解析失败，报以下错误：" class="headerlink" title="解析失败，报以下错误："></a>解析失败，报以下错误：</h5></li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230914214515079.png" alt="image-20230914214515079"></p>
<ul>
<li>最后优化代码，同时处理两种情况：服务器首次连接成功后的响应（<strong>普通字符串</strong>） / 服务器转发的消息（<strong>JSON 字符串</strong>）</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">//获得消息事件(获得服务端转发的消息)</span><br>socket.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">msg</span>) &#123;<br>  <span class="hljs-comment">// 封装返回消息</span><br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> msg === <span class="hljs-title class_">String</span>) &#123;<br>    receiveMsg.<span class="hljs-property">value</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    receiveMsg.<span class="hljs-property">value</span> = msg.<span class="hljs-property">data</span>;<br>  &#125;<br>  <span class="hljs-comment">// 解构</span><br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;这条消息是发给: &quot;</span> + receiveMsg.<span class="hljs-property">value</span>.<span class="hljs-property">receiverId</span> + <span class="hljs-string">&quot; 的&quot;</span>);<br>  <span class="hljs-keyword">const</span> content = receiveMsg.<span class="hljs-property">value</span>.<span class="hljs-property">content</span>;<br>  <span class="hljs-comment">// 是否属于我的消息</span><br>  <span class="hljs-keyword">if</span> (receiverId === currentUserId) &#123;<br>    <span class="hljs-title function_">setMessage</span>(<span class="hljs-string">&quot;服务端回应: &quot;</span> + content + <span class="hljs-string">&quot;发给: &quot;</span> + receiverId);<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="各种坑都能踩到，不过好在花点时间排错，都能解决掉-👏👏👏（2023-09-14-晚）"><a href="#各种坑都能踩到，不过好在花点时间排错，都能解决掉-👏👏👏（2023-09-14-晚）" class="headerlink" title="各种坑都能踩到，不过好在花点时间排错，都能解决掉 👏👏👏（2023/09/14 晚）"></a>各种坑都能踩到，不过好在花点时间排错，都能解决掉 👏👏👏（2023/09/14 晚）</h5></li>
</ul>
<h3 id="双向通信调试成功"><a href="#双向通信调试成功" class="headerlink" title="双向通信调试成功"></a>双向通信调试成功</h3><ul>
<li>首先解决上面小坑，那个代码写的还有点问题，前端还得分两次校验是否为 JSON 字符串</li>
<li>于是我把后端服务器首次响应时的响应结果，转换成 JSON 字符串了</li>
</ul>
<p><img src="http://blog.memory-life.xyz/image-20230915145545165.png" alt="image-20230915145545165"></p>
<ul>
<li>又写了好多打印输出代码，好歹完成了核心功能：<strong>服务器向指定用户转发消息</strong>（<strong>2023/09/15 午</strong>）</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">//获得消息事件(获得服务端转发的消息)</span><br>socket.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">msg</span>) &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;收到消息！消息为: &quot;</span> + msg.<span class="hljs-property">data</span>);<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;消息格式为: &quot;</span> + <span class="hljs-keyword">typeof</span> msg.<span class="hljs-property">data</span>);<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;消息发送者为: &quot;</span> + <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>).<span class="hljs-property">senderId</span>);<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;消息内容为: &quot;</span> + <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>).<span class="hljs-property">content</span>);<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;消息接收者为: &quot;</span> + <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>).<span class="hljs-property">receiverId</span>);<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您是: &quot;</span> + currentUserId);<br>  <span class="hljs-keyword">if</span> (currentUserId === <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>).<span class="hljs-property">receiverId</span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;很好，您是这条消息的接收者！&quot;</span>);<br>    <span class="hljs-title function_">setMessage</span>(<br>      <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>).<span class="hljs-property">senderId</span> +<br>        <span class="hljs-string">&quot;给您发送了一条消息: &quot;</span> +<br>        <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>).<span class="hljs-property">content</span><br>    );<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<p><img src="http://blog.memory-life.xyz/image-20230915145835165.png" alt="image-20230915145835165"></p>
<p><img src="http://blog.memory-life.xyz/image-20230915150018962.png" alt="image-20230915150018962"></p>
<ul>
<li><h5 id="这里还有好多小坑：（2023-09-15-午）"><a href="#这里还有好多小坑：（2023-09-15-午）" class="headerlink" title="这里还有好多小坑：（2023/09/15 午）"></a>这里还有好多小坑：（2023/09/15 午）</h5><ul>
<li>服务器根据在线人数（即连接的客户端数量），决定转发多少条消息，所以控制台输出了不止一条消息（重复的消息）</li>
<li>在 socket 服务下修改了代码之后，有时需<strong>重启浏览器</strong>才生效，这种情况还是第一次遇到</li>
<li>当然了，为了测试实时双向通信，我分别开启了<strong>Edge 浏览器和 Goole 浏览器</strong>进行测试</li>
</ul>
</li>
</ul>
<h3 id="部署和维护"><a href="#部署和维护" class="headerlink" title="部署和维护"></a>部署和维护</h3><ul>
<li>列出将项目部署到生产环境所需的步骤和配置信息，包括服务器环境要求和数据库设置。</li>
<li>提供项目的维护和支持方式，如 bug 提交、技术支持联系方式等。</li>
</ul>
<h2 id="TODO"><a href="#TODO" class="headerlink" title="TODO"></a>TODO</h2><h3 id="添加好友-1"><a href="#添加好友-1" class="headerlink" title="添加好友"></a>添加好友</h3><ul>
<li>用户添加好友上限字段，一个用户应该有好友上限</li>
<li>用户不应该直接添加好友，应该发送好友申请，待对方同意后，二者成为好友关系（<strong>如何判断？</strong>）</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">QueryWrapper&lt;Friends&gt; fqw = <span class="hljs-keyword">new</span> <span class="hljs-title class_">QueryWrapper</span>&lt;&gt;();<br>   fqw.eq(<span class="hljs-string">&quot;user_id&quot;</span>, userId).eq(<span class="hljs-string">&quot;friend_id&quot;</span>, friend.getId());<br></code></pre></td></tr></table></figure>

<ul>
<li>添加成功/失败后的反馈</li>
<li>添加好友后，可支持修改好友昵称</li>
</ul>
<h3 id="分页查询"><a href="#分页查询" class="headerlink" title="分页查询"></a>分页查询</h3><ul>
<li>后台的分页查询未优化，分页参数写死为 <strong>currentPage：1，pageSize：20</strong></li>
</ul>
<h3 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h3><ul>
<li>抛出的异常可以设置独有的错误码，前端妥善解决，优化用户体验</li>
<li>可以考虑封装一个简单的异常处理工具（ThrowUtils）</li>
</ul>
<h3 id="用户体验"><a href="#用户体验" class="headerlink" title="用户体验"></a>用户体验</h3><ul>
<li>发出添加好友、申请入队请求后，抽屉关闭（？）</li>
<li><strong>用户在线问题需要优化</strong>，应该存放进 Redis 中，设置过期时间为 2hour ，现在是直接从数据库中取得</li>
<li>而最大的问题是，<strong>用户登录后，修改用户状态为在线</strong>，仅用户<strong>执行登出操作</strong>后，才会<strong>修改用户状态为下线</strong></li>
<li>用户进入聊天页面，保持聊天窗口滚动条保持在最底部</li>
<li>私聊时，展示用户昵称</li>
<li>聊天消息目前是设置 2hour 后过期，这其实比较不合理，但是处于测试阶段，测试的通信记录开销还蛮大的，删了挺好</li>
<li>日后再考虑如何优化：<strong>定期删除（7 天）</strong> + <strong>用户手动清空聊天记录</strong>，这个更加合理</li>
<li><h5 id="聊天页面刷新，会拿不到登录用户的信息，待修复"><a href="#聊天页面刷新，会拿不到登录用户的信息，待修复" class="headerlink" title="聊天页面刷新，会拿不到登录用户的信息，待修复"></a>聊天页面刷新，会拿不到登录用户的信息，待修复</h5></li>
<li>私聊操作应该更加多元化，可以发送<strong>文本消息</strong>、<strong>表情</strong>、甚至是<strong>收发文件</strong></li>
</ul>
<h3 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h3><ul>
<li>考虑封装一个 RedisUtils，统一所有的 Redis 操作</li>
</ul>
<h3 id="Tab-标签页"><a href="#Tab-标签页" class="headerlink" title="Tab 标签页"></a>Tab 标签页</h3><ul>
<li><h5 id="遇到了-BUG，tab-标签页的图标显示不出来，还想着在聊天窗口添加好友头像呢"><a href="#遇到了-BUG，tab-标签页的图标显示不出来，还想着在聊天窗口添加好友头像呢" class="headerlink" title="遇到了 BUG，tab 标签页的图标显示不出来，还想着在聊天窗口添加好友头像呢"></a>遇到了 BUG，tab 标签页的图标显示不出来，还想着在聊天窗口添加好友头像呢</h5></li>
</ul>
<h3 id="好友私聊-1"><a href="#好友私聊-1" class="headerlink" title="好友私聊"></a>好友私聊</h3><ul>
<li>区分聊天双方，可以在聊天窗口内添加用户昵称，也可以实现：双方的消息气泡在窗口的不同侧</li>
<li>收到消息可以弹出消息提示，提示未读消息</li>
<li>刷新聊天大厅，与服务器的连接丢失</li>
</ul>

                
              </div>
            
            <hr/>
            <div>
              <!-- 文章页面 - 底部标签 -->
<div class="post-metas my-3">
  
  <div class="post-meta mr-3 d-flex align-items-center">
    <i class="iconfont icon-category"></i>
    <!-- 归档页 - 文章列表 -->


<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/blog/categories/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95/" class="category-chain-item">项目志录</a>
  
  

      </span>
    
  
</span>

  </div>
   
  <div class="post-meta">
    <i class="iconfont icon-tags"></i>
    
    <a href="/blog/tags/Redis/">#Redis</a>
    
    <a href="/blog/tags/%E9%A1%B9%E7%9B%AE/">#项目</a>
    
    <a href="/blog/tags/%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C/">#开发经验</a>
    
    <a href="/blog/tags/Elasticsearch/">#Elasticsearch</a>
    
  </div>
  
</div>


              <!-- 文章页面 - 底部标签下方 -->

  
  <div class="license-box my-3">
    <div class="license-title">
      <div>项目志录：Memory 缘忆交友社区开发文档</div>
      <div>https://test.atomgit.net/blog/2023/09/10/项目志录：Memory 缘忆交友社区开发文档/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>Memory</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2023年9月10日</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>更新于</div>
          <div>2023年9月26日</div>
        </div>
      
      
        <div class="license-meta-item">
          <div>许可协议</div>
          <div>
            
              
              
                <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - 署名">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/blog/2023/09/13/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BD%BF%E7%94%A8%20Spring%20Boot%20%E9%9B%86%E6%88%90%20WebSocket%20%E5%AE%9E%E7%8E%B0%E5%89%8D%E5%90%8E%E7%AB%AF%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/" title="实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/blog/2023/08/26/%E9%A1%B9%E7%9B%AE%E5%BF%97%E5%BD%95%EF%BC%9AMemory%20Search%20%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="项目志录：Memory Search 聚合搜索平台开发文档">
                        <span class="hidden-mobile">项目志录：Memory Search 聚合搜索平台开发文档</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            <!-- 文章页 - 评论上方 -->

  
  
    <article id="comments">
      
  <div id="valine"></div>
  <script type="text/javascript">
    Fluid.utils.loadComments('#valine', function() {
      Fluid.utils.createScript('https://cdn.jsdelivr.net/gh/HCLonely/Valine@latest/dist/Valine.min.js', function() {
        var options = Object.assign(
          {"appId":"T1hUHH9Ks1ggG9DBE3HgUPpV-gzGzoHsz","appKey":"XFkgzzvzNo3J3uNCbBiSOFKc","path":"window.location.pathname","placeholder":"填取QQ号和邮箱后评论，支持读取头像及消息提醒，留下点什么叭... ᶘ ᵒᴥᵒᶅ","avatar":"Gravatar","meta":["nick","mail","link"],"requiredFields":[],"pageSize":10,"lang":"zh-CN","highlight":true,"recordIP":false,"serverURLs":"https://t1huhh9k.lc-cn-n1-shared.com","emojiCDN":null,"emojiMaps":null,"enableQQ":true,"master":"8c9b6d6637904da62d74d019cc6982de","friends":"0188229a9cfc284176ea9135cb9514cc","visitor":true},
          {
            el: "#valine",
            path: window.location.pathname,
            app_id: "",
            app_key: "",
            placeholder: "填取QQ号和邮箱后评论，支持读取头像及消息提醒，留下点什么叭... ᶘ ᵒᴥᵒᶅ",
            avatar: "Gravatar",
            meta: ["nick","mail","link"],
            pageSize: "10",
            lang: "zh-CN",
            highlight: true,
            recordIP: false,
            serverURLs: "https://t1huhh9k.lc-cn-n1-shared.com",

            // 设置Bilibili表情包地址
            emojiCDN: '//i0.hdslb.com/bfs/emote/', 
            // 表情title和图片映射
            emojiMaps: {
                "tv_白眼": "48f75163437445665a9be80bb316e4cb252c5415.gif@48w_48h.webp",
                "tv_doge": "302d6c88c63ed162c81a49cafe7ed2709e6fb955.gif@48w_48h.webp",
                "tv_坏笑": "5d2572efd09aab5dde9e2a198bb3f9ac1e2a982e.gif@48w_48h.webp",
                "tv_难过": "9c6b41008a67755410f712334c64313df5f91b3f.gif@48w_48h.webp",
                "tv_生气": "1902a5a2df5b5c931d88c12f0feb264b1e109d0d.gif@48w_48h.webp",
                "tv_委屈": "af5a5853edb43a8178a8cb5df707fa5e88143699.gif@48w_48h.webp",
                "tv_斜眼笑": "c66568b471192ca1f62f6ed4384dc1b283ab7508.gif@48w_48h.webp",
                "tv_呆": "d3fa91e4db9215eb1e20ab9da44f1214aa4bda7b.gif@48w_48h.webp",
                "tv_发怒": "3959eb81b952e4fa8d269d98f9e3639172d84073.gif@48w_48h.webp",
                "tv_呕吐": "db58e9442aae26694af18cc1683607cca3a16763.gif@48w_48h.webp",
                "tv_思考": "b63f9146bfd985af014f8d6d4bdb498805be48f9.gif@48w_48h.webp",
                "tv_微笑": "b98656855d782f61cb8edc7f7fca6563ecafff7e.gif@48w_48h.webp",
                "tv_疑问": "fce1b1a0f3b0e39a2dc16a18508dba7b91e929f4.gif@48w_48h.webp",
                "tv_大哭": "cba61f05f3039b02a7ffc0dfcd9d7995df9fdd74.gif@48w_48h.webp",
                "tv_鼓掌": "be106e6b265883a9f28fbe10f7b765701e2618d4.gif@48w_48h.webp",
                "tv_抠鼻": "696d9f93e722144dc2a78aeffc569418fdf3d565.gif@48w_48h.webp",
                "tv_亲亲": "3534ea44ab74bd20352b88c245a06c4b4c46d271.gif@48w_48h.webp",
                "tv_调皮": "fcd967395fd14e4dd5829fa7e8a967ce23205e52.gif@48w_48h.webp",
                "tv_笑哭": "1c2fd1e8c9dde12812f86e5d4cbddd8993d98082.gif@48w_48h.webp",
                "tv_晕": "030040ec5c9ddc9e3d067658c4139e7314ab42f8.gif@48w_48h.webp",
                "tv_点赞": "30ecff401245fb56bcc1cf588d1809ac1ab1607c.gif@48w_48h.webp",
                "tv_害羞": "411a3e459e8580f5bfd9f639a408247c4b509935.gif@48w_48h.webp",
                "tv_睡着": "3c8b5e293261287a6203597e29b3de07df4d18c6.gif@48w_48h.webp",
                "tv_色": "a0c6d99ab0ab63b8648f5283ff72cec04b604828.gif@48w_48h.webp",
                "tv_吐血": "e17e4539e169d14a3389ff147afea760cebe5de5.gif@48w_48h.webp",
                "tv_无奈": "eb4cb5f07cfd177c7e6a7914316717e56d9cc1d0.gif@48w_48h.webp",
                "tv_再见": "344f61609ecce2008520dc8a977b6169215748a9.gif@48w_48h.webp",
                "tv_流汗": "390bccec65eaff536bd5bb2a0c5b8b0bdea47334.gif@48w_48h.webp",
                "tv_偷笑": "7f11e6f7f63e79112b833bd41fa13a83d7cd8474.gif@48w_48h.webp",
                "tv_抓狂":"a476b93ecd8e94ac3257323fd822f91cef212de2.gif@48w_48h.webp",
                "tv_黑人问号":"b609adf664be33224a9923262031165ae3e34cd2.gif@48w_48h.webp",
                "tv_困":"91c2bf34ecf842d7016c01d841db3d4074bd281f.gif@48w_48h.webp",
                "tv_打脸":"b0fad4856e59c1240e448437da3287bb5ce547e5.gif@48w_48h.webp",
                "tv_闭嘴":"a3fc5388b09e945be3f18fe23bfed5874a0285b7.gif@48w_48h.webp",
                "tv_鄙视":"293b5d459e6264ecf314d20937a936fa672ccd1e.gif@48w_48h.webp",
                "tv_腼腆":"30984e8264324f901d19bea85dada7103b695534.gif@48w_48h.webp",
                "tv_馋":"2525c5703c594e5f0752f68db8948773caebde47.gif@48w_48h.webp",
                "tv_可爱":"f92d20f76258bc5f33fc9d7c5e2a1d41fef19a7c.gif@48w_48h.webp",
                "tv_发财":"76131e52c9b033681b4c896c6024d29ef7ec7ec2.gif@48w_48h.webp",
                "tv_生病":"beb94829fe04f1a41bd6ca611e1f6ca9ca169afa.gif@48w_48h.webp",
                "tv_流鼻血":"8ef473f74a849420da712487b2f56ecca1f695f5.gif@48w_48h.webp",
                "tv_尴尬":"e0b84ef5ee3e5b8978e584c7c5a6550c51d15f84.gif@48w_48h.webp",
                "tv_大佬":"14ca0c05382b8741940942b2430b7a8d55c02f7e.gif@48w_48h.webp",
                "tv_流泪":"7e71cde7858f0cd50d74b0264aa26db612a8a167.png@48w_48h.webp",
                "tv_冷漠":"b9cbc755c2b3ee43be07ca13de84e5b699a3f101.png@48w_48h.webp",
                "tv_皱眉":"72ccad6679fea0d14cce648b4d818e09b8ffea2d.png@48w_48h.webp",
                "tv_鬼脸":"0ffbbddf8a94d124ca2f54b360bbc04feb6bbfea.png@48w_48h.webp",
                "tv_调侃":"4bc022533ef31544ca0d72c12c808cf4a1cce3e3.png@48w_48h.webp",
                "tv_目瞪口呆":"0b8cb81a68de5d5365212c99375e7ace3e7891b7.png@48w_48h.webp",
            },

            master: "8c9b6d6637904da62d74d019cc6982de",
            friends: "0188229a9cfc284176ea9135cb9514cc",
            tagMeta: ["博主","友人","访客"],
            visitor: true // 阅读量统计
          }
        )
        new Valine(options);
        Fluid.utils.waitElementVisible('#valine .vcontent', () => {
          var imgSelector = '#valine .vcontent img:not(.vemoji)';
          Fluid.plugins.imageCaption(imgSelector);
          Fluid.plugins.fancyBox(imgSelector);
        })
      });
    });
  </script>
  <noscript>Please enable JavaScript to view the comments</noscript>


    </article>
  


          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      <!-- 文章页面 - 右侧目录 -->

  <aside class="sidebar" style="margin-left: -1rem">
    <!-- 文章页面 - 右侧目录上方 -->
<div id="toc">
  <p class="toc-header">
    <i class="iconfont icon-list"></i>
    <span>目录</span>
  </p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>



    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <!-- 博客 - 搜索框 -->
<div
  class="modal fade"
  id="modalSearch"
  tabindex="-1"
  role="dialog"
  aria-labelledby="ModalLabel"
  aria-hidden="true"
>
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">
          搜索
        </h4>
        <button
          type="button"
          id="local-search-close"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input
            type="text"
            id="local-search-input"
            class="form-control validate"
          />
          <label data-error="x" data-success="v" for="local-search-input"
            >关键词</label
          >
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
        <div class="modal fade" id="modalNotice" tabindex="-1" role="dialog" aria-hidden="true"
     style="font-family: 'Ma Shan Zheng', cursive; color: #ffffff;">
    <div class="modal-dialog modal-dialog-scrollable" role="document"
         style="font-family: 'Ma Shan Zheng', cursive;">
        <div class="modal-content" style="border-radius: 6px;">
            <!-- 公告头部 - 响应式设计 -->
            <div class="modal-header text-center"
                 style="padding: 1rem 1.5rem;border-bottom: 1px solid rgba(255,255,255,0.2)">
                <h4 class="modal-title w-100 font-weight-bold"
                    style="font-family: 'Microsoft YaHei', sans-serif;letter-spacing: 1px;text-shadow: 1px 1px 2px rgba(0,0,0,0.2);color: #ffffff;
                           font-size: 1.5rem; /* 默认PC端大小 */
                           /* 移动端适配 */
                           @media (max-width: 768px) {
                               font-size: 1.2rem;
                           }">
                    <div class="notice-title">
                        <i class="fas fa-bullhorn mr-2"> 围栏告示</i>
                    </div>
                    <small style="display: block; font-family: 'STKaiti', serif; font-size: 0.6em; color: #a0a0a0; margin-top: 3px;">
                        旧梦轻拾 · 谨识
                    </small>
                </h4>
            </div>

            <!-- 公告内容区 - 响应式设计 -->
            <div class="modal-body"
                 style="padding: 2rem; /* PC端 */
                        max-height: 70vh;
                        overflow-y: auto;
                        /* 移动端适配 */
                        @media (max-width: 768px) {
                            padding: 1rem;
                            max-height: 60vh;
                        }">
                <!-- 图片容器（响应式设计） -->
                <!--                <div class="announcement-hero"-->
                <!--                     style="margin-bottom: 2rem; /* PC端 */-->
                <!--                            /* 移动端适配 */-->
                <!--                            @media (max-width: 768px) {-->
                <!--                                margin-bottom: 1rem;-->
                <!--                            }">-->
                <!--                    <img src="http://blog.memory-life.icu/%E3%80%90%E5%93%B2%E9%A3%8E%E5%A3%81%E7%BA%B8%E3%80%91%E5%8A%A8%E6%BC%AB-%E5%A4%95%E9%98%B3%E9%93%81%E8%BD%A8.jpg"-->
                <!--                         alt="时光溯洄题图"-->
                <!--                         style="width: 100%; -->
                <!--                                max-height: 400px; /* PC端 */-->
                <!--                                object-fit: cover;-->
                <!--                                border-radius: 4px; -->
                <!--                                box-shadow: 0 2px 12px rgba(0,0,0,0.1);-->
                <!--                                /* 移动端适配 */-->
                <!--                                @media (max-width: 768px) {-->
                <!--                                    max-height: 30vh;-->
                <!--                                }"-->
                <!--                         loading="lazy">-->
                <!--                </div>-->

                <!-- 静态公告列表 - 响应式设计 -->
                <div class="announcement-list">
                    <div class="announcement-item"
                         style="margin-bottom: 2rem; /* PC端 */
                                padding: 2rem;
                                background: rgba(255,255,255,0.05);
                                border-radius: 8px;
                                /* 移动端适配 */
                                @media (max-width: 768px) {
                                    margin-bottom: 1rem;
                                    padding: 1rem;
                                }">
                        <h2 style="color: #d38532;
                                  font-weight: 600;
                                  font-size: 1.5rem; /* PC端 */
                                  margin-bottom: 1.2rem;
                                  letter-spacing: 0.5px;
                                  /* 移动端适配 */
                                  @media (max-width: 768px) {
                                      font-size: 1.2rem;
                                      margin-bottom: 1rem;
                                  }">
                            <i class="fas fa-calendar-check mr-2"></i>时光溯洄
                            <small style="display: block; font-size: 0.6em; color: #b8b8b8;text-align: right;">——
                                晨昏往事</small>
                        </h2>
                        <div style="color: #e0e0e0;
                                  line-height: 1.8; /* PC端 */
                                  font-size: 1.05rem;
                                  letter-spacing: 0.3px;
                                  /* 移动端适配 */
                                  @media (max-width: 768px) {
                                      line-height: 1.6;
                                      font-size: 0.95rem;
                                  }">
                            <!-- 内容保持不变 -->
                            <p style="margin-bottom: 1rem; text-indent: 2em;">你好。</p>
                            <!-- 其余段落内容... -->
                            <p style="margin-bottom: 1rem; text-indent: 2em;">
                                建站后的第813天，在百忙之中总算又能抽出时间来，对整个博客站点功能做整体的规划及优化完善。</p>
                            <p style="margin-bottom: 1rem; text-indent: 2em;">
                                在刚刚过去的几年时间里，往事皆以笔墨挥毫于时光素笺之上，数百天如一日，故事的书写从未间断过。</p>
                            <p style="margin-bottom: 1rem; text-indent: 2em; font-style: italic; color: #b8b8b8;">
                                时光悠悠，日月如流，过往的蹉跎岁月总在不经意间在心间回眸。</p>
                            <p style="margin-bottom: 1rem; text-indent: 2em; font-weight: 500; color: #ffffff;">
                                俯仰之间，轻舟已过万重山。</p>
                            <p style="margin-bottom: 1rem; text-indent: 2em; font-weight: 500; color: #ffffff;">
                                蹉跎之处，往事皆成云烟散。</p>
                            <p style="margin-bottom: 1rem; text-indent: 2em;">
                                这几年的时光过得太快了，好像过去这段岁月里的所有事情都发生在一瞬间。</p>
                            <p style="margin-bottom: 1rem; text-indent: 2em;">
                                这几年的时光同样太久了，久到那些时日里仿佛囊括了自己的整个前半生。</p>
                            <p style="margin-bottom: 0; text-align: right; font-size: 0.95rem; color: #a0a0a0;">——
                                八百余个昼夜，当时只道是地久天长，如今回望，不过浮光掠影间的一瞬。</p>
                        </div>
                    </div>

                    <!-- 第二个公告项 - 响应式设计 -->
                    <div class="announcement-item"
                         style="margin-bottom: 2rem; /* PC端 */
                                padding: 2rem;
                                background: rgba(255,255,255,0.05);
                                border-radius: 8px;
                                /* 移动端适配 */
                                @media (max-width: 768px) {
                                    margin-bottom: 1rem;
                                    padding: 1rem;
                                }">
                        <h2 style="color: #6cc70b;
                                  font-weight: 600;
                                  font-size: 1.5rem; /* PC端 */
                                  margin-bottom: 1.2rem;
                                  letter-spacing: 0.5px;
                                  /* 移动端适配 */
                                  @media (max-width: 768px) {
                                      font-size: 1.2rem;
                                      margin-bottom: 1rem;
                                  }">
                            <i class="fas fa-book-open mr-2"></i>墨痕新注
                            <small style="display: block; font-size: 0.6em; color: #b8b8b8;text-align: right;">——
                                待起之章</small>
                        </h2>
                        <div style="color: #e0e0e0;
                                  line-height: 1.8; /* PC端 */
                                  font-size: 1.05rem;
                                  letter-spacing: 0.3px;
                                  /* 移动端适配 */
                                  @media (max-width: 768px) {
                                      line-height: 1.6;
                                      font-size: 0.95rem;
                                  }">
                            <!-- 列表内容 - 响应式设计 -->
                            <ul style="padding-left: 1.8rem; /* PC端 */
                                      margin-bottom: 1.5rem;
                                      /* 移动端适配 */
                                      @media (max-width: 768px) {
                                          padding-left: 1.5rem;
                                          margin-bottom: 1.2rem;
                                      }">
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">溯源计划：</span>新增博文"溯源计划"，旨在用先进的数字技术留住过去的旧时光，让家族的记忆在数字云端生长发芽，全站博文数量已达90+。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">围栏告示：</span>自去年夏天站点新增了“评论回复”功能后，至今时隔十余月，在芒种前夕本站久违地上线“围栏告示”功能，本公告栏会不定时更新，持续跟进记录最新站点功能调整日志，更多精彩内容请敬请期待。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">图片查看：</span>最近几周时间里，考虑到图文流量费用收取问题，暂时关闭了七牛云对象存储私有空间访问权限，故博文内的图文无法正常预览，择日考虑开启访问权限。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">壁纸下载：</span>后续计划实现壁纸下载功能，均为个人收集的共150+超高分辨率精美壁纸，提供在线预览以及下载功能。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">阅读体验：</span>规划完善文章内分页功能，并提供“最近更新”的文章列表，计划收录更新率较高的文章，譬如：阳台日记、叙事之外、岁月如歌、游戏人生等等，尽可能多的提升在线浏览和阅读体验。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">友链完善：</span>持续优化完善友链页面，整理归档个人常用的高质量网站合集，无偿分享给有缘人。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">音乐餐厅：</span>历经十余小时不间断调研和尝试，本站成功上线”音乐餐厅”功能，目前仅收录了个人在QQ音乐平台积累的两份歌单，再匆忙也要记得点播一首喜欢的音乐，没有音乐熏陶的心灵终究是充满缺憾的。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">三言两语：</span>每张照片都是时光的便签，随手一贴就记下了那天，本站结合Mamos实现并注入留言板功能，随缘记录那些藏在照片背后的故事。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">旧梦轻拾：</span>更新全站博客文章所属分类，共设有代码锋芒、部署之道、旧梦轻拾、人间片刻等十余种分类。
                                </li>
                                <li style="margin-bottom: 0.8rem; /* PC端 */
                                         position: relative;
                                         list-style-type: none;
                                         /* 移动端适配 */
                                         @media (max-width: 768px) {
                                             margin-bottom: 0.6rem;
                                         }">
                                    <span style="position: absolute;
                                               left: -1.8rem; /* PC端 */
                                               color: #6cc70b;
                                               /* 移动端适配 */
                                               @media (max-width: 768px) {
                                                   left: -1.5rem;
                                               }">◆</span>
                                    <span style="font-weight: bolder;">方寸之间：</span>博客应用版发布，更便捷的阅读体验来了！现已正式推出独立的桌面应用程序与移动端应用程序，暂时通过网盘和代码库提供安装包。
                                </li>
                                <p style="margin-bottom: 0; text-align: right; font-size: 0.9rem; color: #8a8a8a;">
                                    此公告最后更新于：<span style="color: #6cc70b;">2025/09/30晚</span>
                                </p>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 公告页脚 - 响应式设计 -->
            <div class="modal-footer"
                 style="border-top: 1px solid rgba(255,255,255,0.2);
                        padding: 1rem 1.5rem; /* PC端 */
                        justify-content: space-between;
                        /* 移动端适配 */
                        @media (max-width: 768px) {
                            padding: 0.8rem;
                        }">
                <small style="color: #ffffff;
                            font-size: 0.9rem; /* PC端 */
                            /* 移动端适配 */
                            @media (max-width: 768px) {
                                font-size: 0.8rem;
                            }">
                    <i class="iconfont icon-pen"></i>
                    <i class="fas fa-info-circle mr-1"></i>芒种前夕 · 旅途第813天
                    · 2025/05/28
                </small>
                <button id="confirm-btn" type="button" class="btn btn-sm" data-dismiss="modal"
                        style="background: #a0a0a0;
                               color: #ffffff;
                               border-radius: 4px;
                               padding: 0.5rem 1.2rem; /* PC端 */
                               border: none;
                               font-size: 0.9rem;
                               /* 移动端适配 */
                               @media (max-width: 768px) {
                                   padding: 0.3rem 0.8rem;
                                   font-size: 0.85rem;
                               }">
                    <i class="fas fa-check mr-1" style="color: #ffffff;">心已阅</i>
                </button>
            </div>
        </div>
    </div>
</div>

<style>
  .notice-title {
    display: flex;
    align-items: center;
    white-space: nowrap;
  }

  .notice-title:before,
  .notice-title:after {
    content: "";
    flex: 1;
    height: 1px;
    background: #ddd; /* 横线颜色 */
    margin: 0 10px; /* 横线与文字的间距 */
  }
</style>


<style>
  /* 响应式模态框宽度 */
  @media (min-width: 992px) {
    #modalNotice .modal-dialog {
      max-width: 800px;
    }
  }

  @media (max-width: 991px) {
    #modalNotice .modal-dialog {
      margin: 0.5rem auto;
    }
  }
</style>

<script>
  // 页面加载完成后执行
  document.addEventListener('DOMContentLoaded', function () {
    // 检查是否首次访问
    if (!localStorage.getItem('hasShownPopup')) {
      // 动态加载notice.ejs内容
      fetch('_partials/notice.ejs')  // 替换为实际路径
        .then(response => response.text())
        .then(html => {
          // 插入到页面body末尾
          document.body.insertAdjacentHTML('beforeend', html);

          // 手动初始化Bootstrap模态框
          $('#modalNotice').modal('show');

          // 标记为已显示
          localStorage.setItem('hasShownPopup', 'true');

          // 监听关闭事件
          $('#modalNotice').on('hidden.bs.modal', function () {
            // 可选的后续处理
          });
        })
        .catch(error => {
          console.error('加载公告栏失败:', error);
          // 备用方案：显示简单弹窗
          const fallbackHTML = `
                <div class="modal-backdrop" style="/* 样式 */">
                    <div class="modal-content">/* 内容 */</div>
                </div>`;
          document.body.insertAdjacentHTML('beforeend', fallbackHTML);
        });
    }
  });
</script>

    

    
  </main>

  <footer>
    <!-- 博客 - 页脚 -->
<div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.fluid-dev.com/docs/guide/" target="_blank" rel="nofollow noopener"> <span>Memory | 个人文档站点</span> </a>
<i class="iconfont icon-love"></i> <a href="https://hexo.fluid-dev.com/docs/guide/" target="_blank" rel="nofollow noopener"> <span>配置指南 | Hexo Fluid 用户手册</span> </a> 
    </div>
  

  

  
    <!-- 备案信 ICP for China -->
    <div class="beian">
  <span>
    <a href="http://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener">
      京ICP证123456号
    </a>
  </span>
  
    
      <span>
        <a
          href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=12345678"
          rel="nofollow noopener"
          class="beian-police"
          target="_blank"
        >
          
            <span style="visibility: hidden; width: 0">|</span>
            <img src="/blog/img/police_beian.png" alt="police-icon"/>
          
          <span>京公网安备12345678号</span>
        </a>
      </span>
    
  
</div>

  

  

</div>

<!-- 评论框美化 -->
<style>
    #comments .veditor{
        min-height: 20vh;
        background-image: url(http://blog.memory-life.icu/pathway0716.jpg);
        background-size: contain;
        background-repeat: no-repeat;
        background-position: right;
        background-color: rgba(255,255,255,0);
        resize: none;}
</style>

<script>
    //   自定义邮箱审核规则
    document.body.addEventListener('click', function(e) {
        if (e.target.classList.contains('vsubmit')) {
            const email = document.querySelector('input[type=email]');
            const nick = document.querySelector('input[name=nick]');
            const reg = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
            if (!email.value || !nick.value || !reg.test(email.value)) {
                const str = `<div class="valert text-center"><div class="vtext">请填写正确的昵称和邮箱！</div></div>`;
                const vmark = document.querySelector('.vmark');
                vmark.innerHTML = str;
                vmark.style.display = 'block';
                setTimeout(function() {
                    vmark.style.display = 'none';
                    vmark.innerHTML = '';
                }, 2500);
            }
        }
    })
</script>



  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/blog/js/events.js" ></script>
<script  src="/blog/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>







  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/blog/js/local-search.js" ></script>

  <script defer src="/blog/js/leancloud.js" ></script>




    
<script src="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>



<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/blog/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
